Engineering Note

Docker Compose Is Convenient. Plain .env Secrets Are Not.

Introducing Envoyage, Pletor's open source tool for reducing plaintext secret env files in Docker Compose workflows without turning every small deployment into a full secret-management project.

June 22, 2026 · Pletor Engineering dockerconfigurationsecurityopen-source

Docker Compose fits small deployment units well. It is especially useful for local development, PoCs, internal tools, and single-VM operations before a larger platform becomes necessary.

The awkward part is secret handling. A Compose project naturally grows a .env file, and sooner or later that file may contain database passwords, API keys, admin passwords, or other sensitive values. Plaintext env files are easy to leave behind.

They get committed by accident.
They end up in backups.
They stay in deployment directories.
They are visible in shared workspaces.
They leave traces around shell history and operating scripts.

Vault, KMS, Kubernetes Secret, and external secret managers are good answers. But not every Compose-based deployment can justify that operational weight. For small services and internal tools, the ceremony can become larger than the problem.

That is why Pletor has released Envoyage (/ˈɛn.vɔɪ.ɪdʒ/) as open source.

Envoyage is a lightweight encrypted environment-variable loader for Docker Compose. It decrypts .env.age files in memory, parses the decrypted dotenv content, and passes the resulting variables to docker compose as child-process environment variables. The key point is that it does not write a decrypted plaintext .env file back to disk.

Illustration of a glowing encrypted configuration file inside a transparent glass cube with safe data streams flowing toward container blocks
Envoyage keeps plaintext secret files off disk and passes decrypted values to Compose only at runtime.

Where Envoyage Fits

Envoyage is not trying to replace a full secret manager. Its position is narrower and more practical.

plain .env file
  -> Envoyage
  -> Vault, KMS, platform-level secret management

There are Compose deployments where a plaintext .env feels too casual, but a central secret-management system is too much machinery. Envoyage is built for that middle space.

The basic workflow is small:

  1. Keep non-secret settings in .env.
  2. Keep editable secret source values in .secrets.env.
  3. Encrypt .secrets.env into .env.age.
  4. Run Docker Compose through envoyage compose ....

The deployment directory can keep .env and .env.age, while the plaintext secret source .secrets.env is removed or kept out of Git.

Quickstart

The repository includes examples for PostgreSQL, MariaDB, Redis, MongoDB, and MinIO. This walkthrough uses the PostgreSQL example.

git clone https://github.com/pletorco/envoyage.git
cd envoyage
go build -o envoyage ./cmd/envoyage
cd examples/postgresql

Generate an example age identity.

../../envoyage keygen --out age-key.txt

Encrypt .secrets.env into .env.age.

AGE_IDENTITY_FILE=./age-key.txt ../../envoyage encrypt

Now place Envoyage in front of Compose when you inspect or start the stack.

AGE_IDENTITY_FILE=./age-key.txt ../../envoyage compose -f compose.yaml config
AGE_IDENTITY_FILE=./age-key.txt ../../envoyage compose -f compose.yaml up -d

Cleanup still uses ordinary Compose.

docker compose -f compose.yaml down -v

The committed .secrets.env files in the examples are there for copy-and-run demos. In real projects, do not commit plaintext secrets. Keep .env.age and define your own policy for distributing the age identity.

What Changes Internally

Ordinary Compose usage looks like this:

docker compose up -d

With Envoyage, the shape becomes:

envoyage compose up -d

By default, Envoyage automatically reads .env and .env.age from the current directory when they exist. It loads .env first and .env.age second. Later values override earlier values when keys overlap.

That makes it natural to split settings like this.

DB_HOST=postgres
DB_USER=app
DB_PORT=5432
DB_PASSWORD=secret-password
API_KEY=secret-api-key

The first block is ordinary non-secret configuration for .env. The second block is plaintext source material for .secrets.env before it is encrypted into .env.age.

Explicit env files are also supported.

envoyage compose --env-file .env --env-file .env.age config

Envoyage consumes --env-file and --identity first, then forwards the remaining arguments to the real Docker Compose command.

envoyage compose --env-file .env.age -f compose.yaml -p myapp up -d

That continues as a Compose call shaped like:

docker compose -f compose.yaml -p myapp up -d

The difference is that the Compose process receives the environment variables that Envoyage decrypted and merged.

Docker-Shaped Shim Mode

Envoyage 0.2.0 also adds optional Docker shim mode.

This mode is active only when the Envoyage binary is executed with the name docker, usually through a symlink placed earlier in PATH than the real Docker CLI.

envoyage shim install
export PATH="$HOME/.local/bin:$PATH"
export ENVOYAGE_DOCKER_BIN=/usr/bin/docker

After that, an existing script can keep calling:

docker compose --env-file .env.age up -d

Only docker compose ... is intercepted by Envoyage. Other Docker commands such as docker ps and docker version pass through to the real Docker binary.

This is useful when changing existing operating scripts would be noisy. It also deserves care: because the mode depends on PATH, operators should be explicit about which docker binary is being executed. The README recommends setting ENVOYAGE_DOCKER_BIN to avoid ambiguity.

The Security Boundary

Envoyage reduces the risk of accidentally leaving plaintext secret files at rest. That is a real operational problem.

It does not write decrypted dotenv content to disk. It also avoids intentionally printing secret values in its own logs or errors. This can reduce the chance of leaking plaintext secrets through Git, backups, deployment directories, and shared workspaces.

But this is not a hard security boundary.

The decrypted values still become environment variables for the docker compose child process. Users with Docker permissions, root or superuser access, or access to inspect container environments may still see them. A user who can read the default identity path, /etc/envoyage/envoyage-key.txt, can decrypt .env.age files encrypted for that recipient.

Envoyage also does not provide:

  • Central secret management like Vault or KMS
  • TTL, revoke, or rotation policy
  • Protection from root or Docker administrators
  • Automatic handling of env_file: .env.age inside compose.yaml

That boundary matters. Envoyage does not mean secret management is “done.” It means a small Compose deployment can avoid keeping plaintext secret env files around.

When It Is a Good Fit

Envoyage is a good fit when:

  • You operate a small Docker Compose deployment.
  • You want to keep .env for non-secret configuration but avoid plaintext secrets.
  • Vault or KMS would add too much operational weight.
  • You want to preserve the existing Compose command shape.
  • You want fewer accidents involving plaintext secret files in Git or backups.

It is not the right answer when you need organization-wide secret lifecycle management, access control, audit logs, rotation, or dynamic credentials. In Kubernetes-centered environments, Kubernetes Secret, External Secrets Operator, or cloud secret-manager integrations may be the more natural place to start.

Good Operations Often Start with Small Habits

Operational problems do not only come from large, complex systems. A single .env file committed by accident, left in a deployment directory, or bundled into a backup can be enough.

Envoyage is a small tool for reducing that class of mistake. It keeps the simplicity of Docker Compose, moves secret values into an encrypted file, and decrypts them only at execution time before passing them to Compose.

Konduo is built with a similar operational bias. Observing resources, metrics, and alerts matters, but so does keeping configuration and deployment flows simple and repeatable. Even in Compose-based PoCs and small environments, that discipline makes systems easier to explain before they grow into larger platforms.

Envoyage is not a large secret platform. It is focused on one practical problem: reducing plaintext secret files in small Compose operations. Used for that problem, the size of the tool and the value it provides line up well.

Further Reading

For more context connected to this topic, these posts are also worth reading.