@alchemy.run/docker 2.0.0-beta.84 → 2.0.0-beta.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +16 -0
  2. package/lib/Bootstrap.d.ts.map +1 -1
  3. package/lib/Bootstrap.js +2 -1
  4. package/lib/Bootstrap.js.map +1 -1
  5. package/lib/BuildCache.d.ts +87 -0
  6. package/lib/BuildCache.d.ts.map +1 -0
  7. package/lib/BuildCache.js +39 -0
  8. package/lib/BuildCache.js.map +1 -0
  9. package/lib/BuildHash.d.ts +20 -3
  10. package/lib/BuildHash.d.ts.map +1 -1
  11. package/lib/BuildHash.js +60 -21
  12. package/lib/BuildHash.js.map +1 -1
  13. package/lib/BuildPlan.d.ts +202 -0
  14. package/lib/BuildPlan.d.ts.map +1 -0
  15. package/lib/BuildPlan.js +514 -0
  16. package/lib/BuildPlan.js.map +1 -0
  17. package/lib/Container.d.ts +122 -1
  18. package/lib/Container.d.ts.map +1 -1
  19. package/lib/Container.js +232 -4
  20. package/lib/Container.js.map +1 -1
  21. package/lib/Context.d.ts +20 -1
  22. package/lib/Context.d.ts.map +1 -1
  23. package/lib/Context.js +15 -0
  24. package/lib/Context.js.map +1 -1
  25. package/lib/Docker.d.ts +113 -1
  26. package/lib/Docker.d.ts.map +1 -1
  27. package/lib/Docker.js +50 -8
  28. package/lib/Docker.js.map +1 -1
  29. package/lib/Dockerfile.d.ts +0 -2
  30. package/lib/Dockerfile.d.ts.map +1 -1
  31. package/lib/Dockerfile.js.map +1 -1
  32. package/lib/DockerfileParser.d.ts +125 -0
  33. package/lib/DockerfileParser.d.ts.map +1 -0
  34. package/lib/DockerfileParser.js +1040 -0
  35. package/lib/DockerfileParser.js.map +1 -0
  36. package/lib/HealthcheckCommand.d.ts.map +1 -1
  37. package/lib/HealthcheckCommand.js.map +1 -1
  38. package/lib/Image.d.ts +69 -13
  39. package/lib/Image.d.ts.map +1 -1
  40. package/lib/Image.js +59 -9
  41. package/lib/Image.js.map +1 -1
  42. package/lib/ImageArchive.d.ts +41 -0
  43. package/lib/ImageArchive.d.ts.map +1 -0
  44. package/lib/ImageArchive.js +54 -0
  45. package/lib/ImageArchive.js.map +1 -0
  46. package/lib/ImageSource.d.ts +151 -8
  47. package/lib/ImageSource.d.ts.map +1 -1
  48. package/lib/ImageSource.js +247 -72
  49. package/lib/ImageSource.js.map +1 -1
  50. package/lib/Network.d.ts +175 -3
  51. package/lib/Network.d.ts.map +1 -1
  52. package/lib/Network.js +296 -8
  53. package/lib/Network.js.map +1 -1
  54. package/lib/Providers.d.ts.map +1 -1
  55. package/lib/Providers.js.map +1 -1
  56. package/lib/Registry.d.ts.map +1 -1
  57. package/lib/Registry.js.map +1 -1
  58. package/lib/RegistryError.d.ts.map +1 -1
  59. package/lib/RegistryError.js.map +1 -1
  60. package/lib/RemoteImage.d.ts.map +1 -1
  61. package/lib/RemoteImage.js +1 -1
  62. package/lib/RemoteImage.js.map +1 -1
  63. package/lib/Service.d.ts +24 -3
  64. package/lib/Service.d.ts.map +1 -1
  65. package/lib/Service.js +154 -12
  66. package/lib/Service.js.map +1 -1
  67. package/lib/ServiceImage.d.ts.map +1 -1
  68. package/lib/ServiceImage.js.map +1 -1
  69. package/lib/ShellWord.d.ts +28 -0
  70. package/lib/ShellWord.d.ts.map +1 -0
  71. package/lib/ShellWord.js +301 -0
  72. package/lib/ShellWord.js.map +1 -0
  73. package/lib/Swarm.d.ts.map +1 -1
  74. package/lib/Swarm.js.map +1 -1
  75. package/lib/Volume.d.ts +24 -1
  76. package/lib/Volume.d.ts.map +1 -1
  77. package/lib/Volume.js +16 -0
  78. package/lib/Volume.js.map +1 -1
  79. package/lib/index.d.ts.map +1 -1
  80. package/lib/index.js.map +1 -1
  81. package/package.json +9 -9
  82. package/src/Bootstrap.ts +2 -0
  83. package/src/BuildCache.ts +111 -0
  84. package/src/BuildHash.ts +85 -26
  85. package/src/BuildPlan.ts +758 -0
  86. package/src/Container.ts +361 -5
  87. package/src/Context.ts +27 -1
  88. package/src/Docker.ts +157 -9
  89. package/src/Dockerfile.ts +0 -2
  90. package/src/DockerfileParser.ts +1152 -0
  91. package/src/Image.ts +75 -15
  92. package/src/ImageArchive.ts +77 -0
  93. package/src/ImageSource.ts +391 -99
  94. package/src/Network.ts +432 -10
  95. package/src/RemoteImage.ts +1 -1
  96. package/src/Service.ts +246 -62
  97. package/src/ShellWord.ts +326 -0
  98. package/src/Volume.ts +33 -1
package/src/Image.ts CHANGED
@@ -5,6 +5,8 @@ import { Resource } from "alchemy/Resource";
5
5
  import * as Effect from "effect/Effect";
6
6
  import * as FileSystem from "effect/FileSystem";
7
7
  import * as Path from "effect/Path";
8
+ import type * as Redacted from "effect/Redacted";
9
+ import { type BuildCacheOptions, buildCacheRef, resolveBuildCache } from "./BuildCache.ts";
8
10
  import { Docker, dockerContextName, dockerPhysicalName } from "./Docker.ts";
9
11
  import type { Providers } from "./Providers.ts";
10
12
  import {
@@ -15,7 +17,7 @@ import {
15
17
  withRegistryHost,
16
18
  } from "./Registry.ts";
17
19
 
18
- export interface DockerBuildOptions {
20
+ export interface DockerBuildOptions extends BuildCacheOptions {
19
21
  /**
20
22
  * Build context directory.
21
23
  *
@@ -30,14 +32,20 @@ export interface DockerBuildOptions {
30
32
  dockerfile?: string;
31
33
  /** Target platform, e.g. `"linux/amd64"`. */
32
34
  platform?: string;
33
- /** Docker build arguments. */
35
+ /**
36
+ * Docker build arguments (`--build-arg`). Never pass secrets here: build
37
+ * args are visible in the image history. Use {@link secrets} instead.
38
+ */
34
39
  args?: Record<string, string>;
40
+ /**
41
+ * BuildKit secrets, keyed by id. Mount one in a `RUN` step with
42
+ * `--mount=type=secret,id=<id>` and read it from `/run/secrets/<id>`, or
43
+ * add `env=<NAME>` to expose it as an environment variable for that step.
44
+ * A secret never enters the image, its build history, or `docker`'s argv.
45
+ */
46
+ secrets?: Record<string, Redacted.Redacted<string>>;
35
47
  /** Multi-stage build target. */
36
48
  target?: string;
37
- /** Cache sources passed as `--cache-from`. */
38
- cacheFrom?: string[];
39
- /** Cache destinations passed as `--cache-to`. */
40
- cacheTo?: string[];
41
49
  /** Additional Docker build options. */
42
50
  options?: string[];
43
51
  }
@@ -117,6 +125,23 @@ export interface Image extends Resource<
117
125
  * });
118
126
  * ```
119
127
  *
128
+ * ### Build Secrets
129
+ * **Example:** Install from a private npm registry
130
+ * ```typescript
131
+ * // Dockerfile:
132
+ * // RUN --mount=type=secret,id=NPM_TOKEN,env=NPM_TOKEN npm ci
133
+ * const image = yield* Docker.Image("app", {
134
+ * name: "my-app",
135
+ * build: {
136
+ * context: "./app",
137
+ * secrets: { NPM_TOKEN: Config.Redacted("NPM_TOKEN") },
138
+ * },
139
+ * });
140
+ * ```
141
+ *
142
+ * The secret is mounted only into the `RUN` step that asks for it; it never
143
+ * lands in an image layer or the build history.
144
+ *
120
145
  * ### Registry Push
121
146
  * **Example:** Push with Redacted credentials
122
147
  * ```typescript
@@ -131,18 +156,35 @@ export interface Image extends Resource<
131
156
  * });
132
157
  * ```
133
158
  *
134
- * ### Private Base Images and Registry Caches
135
- * **Example:** Build FROM a private base image with a registry cache
159
+ * ### Private Base Images
160
+ * **Example:** Build FROM a private base image
136
161
  * ```typescript
137
162
  * // Dockerfile: FROM registry.example.com/base:v1
138
163
  * const image = yield* Docker.Image("app", {
139
164
  * name: "registry.example.com/app",
140
- * build: {
141
- * context: "./app",
142
- * cacheFrom: ["type=registry,ref=registry.example.com/app:buildcache"],
143
- * cacheTo: ["type=registry,ref=registry.example.com/app:buildcache,mode=max"],
165
+ * build: { context: "./app" },
166
+ * // Authenticates the base-image pull, then the push.
167
+ * registry: {
168
+ * server: "registry.example.com",
169
+ * username: "deploy",
170
+ * password: Config.Redacted("REGISTRY_PASSWORD"),
144
171
  * },
145
- * // Authenticates the base-image pull and cache import/export, then the push.
172
+ * });
173
+ * ```
174
+ *
175
+ * ### Caching Build Layers
176
+ * Builds reuse the local BuildKit cache of the machine they run on, and
177
+ * nothing but the image is pushed. A builder that has never built the image,
178
+ * such as a fresh CI runner, reruns every step. Set `build.cache: true` to keep
179
+ * every stage's layers at the image repository's `:buildcache` tag, so fresh
180
+ * builders skip unchanged steps. For another backend, pass BuildKit's own
181
+ * `--cache-from` / `--cache-to` values as `from` and `to`.
182
+ *
183
+ * **Example:** Cache layers in the image's registry
184
+ * ```typescript
185
+ * const image = yield* Docker.Image("app", {
186
+ * name: "registry.example.com/app",
187
+ * build: { context: "./app", cache: true },
146
188
  * registry: {
147
189
  * server: "registry.example.com",
148
190
  * username: "deploy",
@@ -151,6 +193,20 @@ export interface Image extends Resource<
151
193
  * });
152
194
  * ```
153
195
  *
196
+ * **Example:** Cache layers in GitHub Actions
197
+ * ```typescript
198
+ * const image = yield* Docker.Image("app", {
199
+ * name: "my-app",
200
+ * build: {
201
+ * context: "./app",
202
+ * cache: {
203
+ * from: ["type=gha,scope=app"],
204
+ * to: ["type=gha,scope=app,mode=max"],
205
+ * },
206
+ * },
207
+ * });
208
+ * ```
209
+ *
154
210
  * ### Docker Context
155
211
  * **Example:** Build in a named Docker context
156
212
  * ```typescript
@@ -192,8 +248,12 @@ export const ImageProvider = () =>
192
248
  platform: props.build.platform,
193
249
  target: props.build.target,
194
250
  "build-arg": props.build.args,
195
- "cache-from": props.build.cacheFrom,
196
- "cache-to": props.build.cacheTo,
251
+ secrets: props.build.secrets,
252
+ // The registry cache lives next to the image.
253
+ ...resolveBuildCache(
254
+ props.build.cache,
255
+ buildCacheRef(props.registry ? withRegistryHost(ref, props.registry) : ref),
256
+ ),
197
257
  args: props.build.options,
198
258
  engineContext,
199
259
  // Base images and registry caches may live in the same registry;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Image archives: a Docker build context built for one platform and saved
3
+ * with `docker save`, for hosts with no registry between the deploy and the
4
+ * machine that runs it (a VM that loads the archive into its own Docker).
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as FileSystem from "effect/FileSystem";
8
+ import * as Path from "effect/Path";
9
+ import { hashDockerBuildInputs, resolveDockerBuildPaths } from "./BuildHash.ts";
10
+ import { Docker } from "./Docker.ts";
11
+
12
+ export interface ImageArchiveSource {
13
+ /** The Docker build context directory. */
14
+ context: string;
15
+ /**
16
+ * The Dockerfile, relative to the context unless absolute.
17
+ * @default "Dockerfile"
18
+ */
19
+ dockerfile?: string;
20
+ /** The platform the archive's image is built for, e.g. `linux/amd64`. */
21
+ platform: string;
22
+ }
23
+
24
+ const pathsOf = Effect.fn(function* (source: ImageArchiveSource) {
25
+ const path = yield* Path.Path;
26
+ const dockerfile =
27
+ source.dockerfile === undefined
28
+ ? path.join(source.context, "Dockerfile")
29
+ : path.isAbsolute(source.dockerfile)
30
+ ? source.dockerfile
31
+ : path.join(source.context, source.dockerfile);
32
+ return yield* resolveDockerBuildPaths({ context: source.context, dockerfile });
33
+ });
34
+
35
+ /**
36
+ * A content hash of the inputs an archive is built from: the context's
37
+ * files (after `.dockerignore`), the Dockerfile and the platform. It
38
+ * changes exactly when the archive's image would.
39
+ */
40
+ export const hashImageArchive = Effect.fn(function* (source: ImageArchiveSource) {
41
+ const { context, dockerfile } = yield* pathsOf(source);
42
+ return yield* hashDockerBuildInputs(
43
+ { context, dockerfile, platform: source.platform },
44
+ "effective",
45
+ );
46
+ });
47
+
48
+ /**
49
+ * Build `source` and save its image to an archive at `output`, tagged
50
+ * `tag`, the name `docker load` reports where the archive is loaded.
51
+ * Resolves to the archive's bytes.
52
+ */
53
+ export const buildImageArchive = Effect.fn(function* (
54
+ source: ImageArchiveSource,
55
+ options: { readonly tag: string; readonly output: string },
56
+ ) {
57
+ const docker = yield* Docker;
58
+ const fs = yield* FileSystem.FileSystem;
59
+ const path = yield* Path.Path;
60
+ const { context, dockerfile } = yield* pathsOf(source);
61
+ yield* docker.image.build({
62
+ context,
63
+ file: dockerfile,
64
+ tag: options.tag,
65
+ platform: source.platform,
66
+ });
67
+ yield* fs.makeDirectory(path.dirname(options.output), { recursive: true });
68
+ yield* docker.run(["save", options.tag, "-o", options.output]);
69
+ return yield* fs.readFile(options.output);
70
+ });
71
+
72
+ /**
73
+ * Shell lines that load the archive at `archive` into Docker and set the
74
+ * shell variable `IMAGE` to the image it holds.
75
+ */
76
+ export const loadImageArchiveScript = (archive: string) =>
77
+ `IMAGE="$(docker load -i ${archive} | sed -n 's/^Loaded image: //p' | tail -1)"`;