Summary
Docker.Container.environment passes variable names to docker container create but does not pass their values into the Docker CLI process environment. Every configured variable becomes an empty container environment entry.
Observed with alchemy@2.0.0-beta.67. The same code is present on current main at dbb98b365e1b8a967007491be825e62ccea7babb.
Reproduction
const container = yield* Docker.Container("EnvProbe", {
image: "alpine:3.22",
command: ["sleep", "300"],
environment: {
PLAIN_VALUE: "plain-value",
SECRET_VALUE: Redacted.make("secret-value"),
},
start: true,
})
Inspect the container:
docker inspect <container> --format '{{json .Config.Env}}'
Expected entries:
["PLAIN_VALUE=plain-value", "SECRET_VALUE=secret-value"]
Actual entries:
["PLAIN_VALUE", "SECRET_VALUE"]
This also prevents PostgreSQL images from bootstrapping. A container configured with POSTGRES_DB, POSTGRES_USER, and redacted POSTGRES_PASSWORD exits with:
Database is uninitialized and superuser password is not specified.
Root cause
DockerLive.run already accepts a process environment and passes it to ChildProcess.make:
|
const run = ( |
|
args: Array<string>, |
|
env?: Record<string, string>, |
|
tap: ( |
|
stream: Stream.Stream<string, PlatformError, never>, |
|
) => Stream.Stream<string, PlatformError, never> = Stream.tap( |
|
Effect.logDebug, |
|
), |
|
) => |
|
ChildProcess.make(bin, args, { |
|
stdin: "ignore", |
|
stdout: "pipe", |
|
stderr: "pipe", |
|
detached: false, |
|
env, |
|
extendEnv: true, |
|
}).pipe( |
container.create intentionally emits --env KEY, which lets Docker copy values from its process environment without exposing secrets on CLI arguments. However, it calls run(args) without the env argument:
|
container: { |
|
create: ({ image, env, command, context, ...options }) => |
|
run([ |
|
...formatArgs({ context }), |
|
"container", |
|
"create", |
|
...formatArgs({ |
|
...options, |
|
env: env ? Object.keys(env) : undefined, |
|
}), |
|
image, |
|
...(command ?? []), |
|
]), |
create: ({ image, env, command, context, ...options }) =>
run([
// ...
...formatArgs({
...options,
env: env ? Object.keys(env) : undefined,
}),
image,
...(command ?? []),
]),
The Docker CLI inherits only the deploy process environment, not values from Docker.Container.environment, so --env KEY resolves without a value.
Suggested fix
Pass the normalized container environment to the Docker CLI child process while keeping values off command arguments:
create: ({ image, env, command, context, ...options }) =>
run([
// existing args
- ]),
+ ], env),
This uses the existing run(args, env) API and matches the Docker documentation promise that redacted values are passed through process environment rather than CLI arguments.
Workaround
We currently apply this pnpm dependency patch to both published runtime and TypeScript source:
diff --git a/lib/Docker/Docker.js b/lib/Docker/Docker.js
@@
- ]),
+ ], env),
diff --git a/src/Docker/Docker.ts b/src/Docker/Docker.ts
@@
- ]),
+ ], env),
With the patch, a real local Alchemy stack successfully bootstraps PostgreSQL using plain and Redacted environment values, applies migrations, seeds fixtures, and completes repeated workerd requests. The patch remains pinned with its upstream issue documented and will be removed after an eligible Alchemy release contains the equivalent fix.
Docker.Service.secrets is not a direct replacement here: it references pre-existing Docker Swarm secrets, while Docker.Container has no secrets property and Alchemy currently exposes no Docker.Secret resource for creating them.
Environment
alchemy@2.0.0-beta.67
- Confirmed unchanged on current
main at dbb98b365e1b8a967007491be825e62ccea7babb
- Node
24.18.0
- pnpm
11.13.0
- Docker Engine
linux/amd64
Summary
Docker.Container.environmentpasses variable names todocker container createbut does not pass their values into the Docker CLI process environment. Every configured variable becomes an empty container environment entry.Observed with
alchemy@2.0.0-beta.67. The same code is present on currentmainatdbb98b365e1b8a967007491be825e62ccea7babb.Reproduction
Inspect the container:
Expected entries:
Actual entries:
This also prevents PostgreSQL images from bootstrapping. A container configured with
POSTGRES_DB,POSTGRES_USER, and redactedPOSTGRES_PASSWORDexits with:Root cause
DockerLive.runalready accepts a process environment and passes it toChildProcess.make:alchemy/packages/alchemy/src/Docker/Docker.ts
Lines 477 to 493 in dbb98b3
container.createintentionally emits--env KEY, which lets Docker copy values from its process environment without exposing secrets on CLI arguments. However, it callsrun(args)without theenvargument:alchemy/packages/alchemy/src/Docker/Docker.ts
Lines 583 to 595 in dbb98b3
The Docker CLI inherits only the deploy process environment, not values from
Docker.Container.environment, so--env KEYresolves without a value.Suggested fix
Pass the normalized container environment to the Docker CLI child process while keeping values off command arguments:
create: ({ image, env, command, context, ...options }) => run([ // existing args - ]), + ], env),This uses the existing
run(args, env)API and matches the Docker documentation promise that redacted values are passed through process environment rather than CLI arguments.Workaround
We currently apply this pnpm dependency patch to both published runtime and TypeScript source:
With the patch, a real local Alchemy stack successfully bootstraps PostgreSQL using plain and
Redactedenvironment values, applies migrations, seeds fixtures, and completes repeated workerd requests. The patch remains pinned with its upstream issue documented and will be removed after an eligible Alchemy release contains the equivalent fix.Docker.Service.secretsis not a direct replacement here: it references pre-existing Docker Swarm secrets, whileDocker.Containerhas no secrets property and Alchemy currently exposes noDocker.Secretresource for creating them.Environment
alchemy@2.0.0-beta.67mainatdbb98b365e1b8a967007491be825e62ccea7babb24.18.011.13.0linux/amd64