Problem: I can manage my entire Docker Compose stack with Git, except secrets.
Solution: Encrypt your secrets using Ojster and safely store them in Git — even inside a public repository. Values can only be decrypted by the server holding the private key.
Docker Compose workflows commonly rely on environment variables. Even when encrypted with tools like Ansible Vault, Dotenvx, or env-vault, decrypted values often end up embedded in container specs, visible in management UIs (Docker Desktop, Portainer), or leaked via docker inspect, logs, or image metadata.
Ojster closes this gap. It provides one-way, quantum-safe encryption (MLKEM + AES) and a hardened decryption server so you can commit encrypted values to Git and decrypt them just in time at container startup — in memory and under strict least-privilege constraints. This zero-trust, ephemeral secrets solution is ideal for self-hosting prebuilt images in a GitOps workflow with Docker Compose.
- Securely store secrets next to Compose files — encrypted values are safe to commit.
- No plaintext secrets in container specs — decrypted values exist only in RAM.
- Minimal integration effort — no need to override entrypoints or commands.
- Anyone can encrypt; only the server can decrypt — public-key, one-way encryption.
- Air-gapped, least-privileged server — no internet, no DNS, immutable rootfs, tmpfs, non-root user, zero capabilities.
- Auditable and lightweight — small Go codebase (compiled by you), no third-party runtime dependencies on the client.
- Pluggable architecture — Ojster ships its own
seal,unseal, andkeypairimplementations (MLKEM + AES) and can also use Dotenvx as an alternative backend if desired.
Get ready to BYOB and safely store encrypted secrets in Git.
Ojster is in an early-stage, pre-release phase. Expect breaking changes as the design evolves, security hardening improves, and real-world feedback shapes the API and integration model. The core concepts are stable, but details may change between versions. Review release notes before upgrading.
On the server that hosts your Docker containers:
git clone https://github.com/ojster/ojster
cd ojster
# Build Your Own Binary (image)
docker bake
# Run containers as the current uid/gid to grant access to the files in this repo
# Not required for Docker Desktop on macOS
# For a production setup it's recommended to use a dedicated server uid/gid
PUID="$(id -u)"; PGID="$(id -g)"
export PUID PGID
# Common docker run flags
COMMON1=(
--user="${PUID:-64646}:${PGID:-64646}"
--pull=never
--read-only
--cap-drop=ALL
--network=none
--security-opt=no-new-privileges=true
)
# Generate a keypair using Ojster's built-in keypair command
docker run "${COMMON1[@]}" --rm -v "$(pwd)":/o ojster/ojster keypair
# Do NOT commit the ojster_priv.key to Git!
# Bring up Ojster server
docker compose up -d
# Encrypt a variable using Ojster's built-in seal command (no private key needed)
# Enter an example secret and press Ctrl-D (twice) when done.
docker run "${COMMON1[@]}" -it --rm -v "$(pwd)":/o ojster/ojster seal EXAMPLE
CLIENT_DIR=examples/01_client
COMMON2=(
--project-name=ojster-client-example
--file=./"$CLIENT_DIR"/compose.base.yaml
--project-directory=.
)
# Bring up example stack WITHOUT Ojster enabled
docker compose "${COMMON2[@]}" up
# Note in output that env var is still encrypted (prefix OJSTER-1:)
# Bring up example stack WITH Ojster enabled
docker compose "${COMMON2[@]}" -f ./"$CLIENT_DIR"/compose.ojster.yaml up
# Note in output that env var is now decrypted
# Cleanup
docker compose "${COMMON2[@]}" down
docker compose down -vIdeally the Ojster server compose.yaml file becomes part of the stack you manage via GitOps, as well a the PUBLIC key, so you can easily add new encrypted environment variables.
Notes
- The examples above use the Ojster-provided
keypairandsealcommands. If you prefer, Ojster can also interoperate with Dotenvx — it is pluggable and works with Dotenvx out of the box.
Add the snippet in compose.ojster.yaml to any service you want to integrate. Ojster acts as a lightweight docker-init replacement and injects decrypted values at process start — no need to modify entrypoints, commands, or rebuild images.
Ojster currently does not support podman. The dockerfile relies on a BuildKit feature which podman/buildah doesn't offer. Additionally podman doesn't support the bake .hcl files, volume.type=image and has a different --init implementation: /run/podman-init. But most importantly podman will throw this error when trying to provide our own init binary in combination with --init: "Error response from daemon: container create: conflict with mount added by --init to "/run/podman-init": duplicate mount destination".
If you know Kubernetes, Ojster is conceptually similar to Bitnami Sealed Secrets — but for Docker Compose.
Shared principles:
- Encrypted secrets stored safely in Git
- One-way encryption — anyone can encrypt; only the private-key holder can decrypt
- Plaintext never appears in configuration files
Sealed Secrets uses a Kubernetes controller. Ojster applies the same pattern to Docker Compose using a lightweight client and a hardened decryption server.
Ojster implements its own MLKEM + AES sealing/unsealing and keypair generation, but it remains pluggable and compatible with Dotenvx. The table below compares Ojster to plain Dotenvx (the free, Open Source version) and Docker secrets.
| Feature | Ojster | dotenvx runoutside container |
dotenvx runinside container |
Docker secrets |
|---|---|---|---|---|
| Secure secrets in Git | ✅ | ✅ | ✅ | ❌ |
| Encrypted env vars in container spec |
✅ | ❌ | ❌ | |
| Quantum-safe encryption | ✅ | ❌ | ❌ | ❌ |
| Unmodified container image |
✅ | ✅ | ❌ | ✅ |
| Native Docker Compose | ✅ | ❌ | ✅ | ✅ |
| Air-gapped private key access |
✅ | ❌ | ❌ | N/A |
| 0 third-party runtime dependencies (client side) |
✅ | ✅ | ❌ | ✅ |
| Image size increase | N/A | N/A | ~50 MB | N/A |
Interpretation
- Ojster integrates encrypted secrets into GitOps Compose workflows with minimal attack surface using post-quantum cryptography (PQC).
dotenvx runoutside container callingdocker compose upstill places decrypted values into container specs (visible to orchestration tooling), requires wrapping around Compose commands, and is not air-gapped (e.g. to prevent a malicious dependency from data exfiltration).dotenvx runinside container requires shipping decryption tooling in each image (823 third-party dependencies) and exposes keys to all containers, increasing attack surface and image size. The official walkthrough sets the private key as plaintext env var in the container spec.- Docker secrets are not encrypted and unsafe to store in Git.
Ojster does not provide the feature set of a full secrets platform. If that's what you need, consider:
- Zero trust — clients never hold private keys.
- Ephemeral secrets — decrypted values never touch disk.
- Minimal client impact — original entrypoints and runtime environments remain unchanged.
- Local IPC transport — server without network stack.
- Selection: client scans environment for values matching
OJSTER_REGEX(configurable). - IPC: client posts
key → encrypted valuemap to the Ojster server over a Unix domain socket. - Decryption: server decrypts using private key or outsources decryption to a user defined subprocess.
- Return: server sends decrypted map back to client.
- Exec: client merges values into the environment and
execs the real entrypoint.
- Configurable subprocess runs in tmp directory containing encrypted
.envand.env.keyssymlinked to private key - Tmpfs enforcement using Linux
statfs - Strict validation of subprocess output
- Minimal logging to avoid leaking secrets
- Configurable regex to detect encrypted values
Securely provision the private key on the Ojster server host. Only the Ojster server should ever have access to the private key. Access to the ojster volume (which contains the IPC socket file) is equivalent to the ability to request decryptions: any process that can open the socket can talk HTTP to the server and obtain decrypted values. Treat the socket like a sensitive IPC endpoint.
- Protect the private key both at rest (encrypted storage or HSM/TPM) and in transit.
- Enforce strict private key file permissions:
chmod 600and ownership matching UID/GID running the Ojster server. - Rotate keys if compromised; re-encrypt secrets as needed.
- Avoid sharing the IPC socket with untrusted containers or services.
- Keep server container hardened: non-root, drop capabilities, set
no-new-privileges, no DNS, no outbound network access, immutable rootfs, tmpfs for tmp files.
Contributing
- Open issues for bugs or feature requests.
- Ensure
./tools/testpasses locally. - Keep changes small and security-aware.
- No third-party runtime dependencies will be accepted.
Testing
./tools/testLicense
Apache License 2.0.
Ojster (pronounced “oyster”) is a metaphor for a protective shell that keeps something valuable sealed away until the moment it’s needed. The J gives the name a distinctive, memorable twist while subtly nodding to its creator. Its nautical undertone fits naturally within the Docker ecosystem.