Skip to content

Docker.Container passes environment names without values #1117

Description

@shekohex

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions