ojster

Ojster – GitOps-safe one-way encrypted secrets

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.

Ojster Logo

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.

Highlights

Get ready to BYOB and safely store encrypted secrets in Git.

🚧 Project status: early stages

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.

Quick start

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 -v

Ideally 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

Integrate your stack

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.

Podman compatibility

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”.

Comparison

Bitnami Sealed Secrets

If you know Kubernetes, Ojster is conceptually similar to Bitnami Sealed Secrets — but for Docker Compose.

Shared principles:

Sealed Secrets uses a Kubernetes controller. Ojster applies the same pattern to Docker Compose using a lightweight client and a hardened decryption server.

Dotenvx and Docker secrets

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 run
outside container
dotenvx run
inside 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

Secret platforms

Ojster does not provide the feature set of a full secrets platform. If that’s what you need, consider:

Technical design and workings

Design goals

High-level flow

  1. Selection: client scans environment for values matching OJSTER_REGEX (configurable).
  2. IPC: client posts key → encrypted value map to the Ojster server over a Unix domain socket.
  3. Decryption: server decrypts using private key or outsources decryption to a user defined subprocess.
  4. Return: server sends decrypted map back to client.
  5. Exec: client merges values into the environment and execs the real entrypoint.

Key implementation details

Security considerations

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.

Recommendations

Contributing and license

Contributing

Testing

./tools/test

License

Apache License 2.0.

Why Ojster?

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.