Coti Secret Manager is a tiny Python utility for handling a single encrypted secret (e.g., a private key) stored as a JSON file, with the password supplied at runtime.
It’s designed for Docker environments where you want:
- the encrypted secret JSON stored on a shared/bind volume (e.g.
./secrets/my-secret.json) - the password injected only at service startup
- the password written to tmpfs inside the container (e.g.
/run/secrets/coti_sk.pw) - the service to wait for the password, decrypt in memory, then delete the password file
Install from GitHub (example):
pip install "git+https://github.com/<you>/coti-secret.git@main"Import name is
coti_secret(underscores), even if the distribution name iscoti-secret.
Recommended production approach:
- install nothing with pip on the host
- use a tool container (part of your
docker-compose.yml) to run encryption and password injection commands
You’ll typically have:
- your main service container (e.g.
my-docker-service) - a helper tool container (e.g.
coti-secret-tool) with Python +coti_secretinstalled
Example compose snippet:
services:
coti-secret-tool:
image: ghcr.io/<you>/coti-secret-tool:latest
volumes:
- ./secrets:/keystore
entrypoint: ["python", "-m", "coti_secret.cli"]
my-docker-service:
image: your-service-image:latest
volumes:
- ./secrets:/keystore:ro
tmpfs:
- /run/secrets:rw,noexec,nosuid,nodev
environment:
ENCRYPTED_FILE: /keystore/my-secret.json
PW_FILE: /run/secrets/coti_sk.pwOption A: run the CLI locally (if you installed with pip):
python -m coti_secret.cli encrypt --out ./secrets/my-secret.json
# paste secret via stdin, then enter passwordOption B: run the CLI via the Docker tool container:
docker compose run --rm coti-secret-tool encrypt --out /keystore/my-secret.json
# paste secret via stdin, then enter passwordThe output file will be written to ./secrets/my-secret.json.
Run encryption inside the tool container so the production host doesn’t need Python tooling installed:
docker compose run --rm coti-secret-tool encrypt --out /keystore/my-secret.jsonYou will:
- paste the secret
- enter the password (hidden)
- the tool writes
./secrets/my-secret.json
Start the service:
docker compose up -dThen inject the password into the running container’s tmpfs (stdin → file). Example:
docker compose exec -T my-docker-service sh -lc "umask 077; cat > /run/secrets/coti_sk.pw; chmod 600 /run/secrets/coti_sk.pw"
# then type/paste the password + Enter, Ctrl-DWhy tmpfs?
Deleting a file on tmpfs is meaningfully safer than attempting to “shred” a disk-backed file (journaling/COW/SSD wear-leveling make disk shredding unreliable).
import os
from coti_secret import SecretManager, SecretType
encrypted_file = os.environ["ENCRYPTED_FILE"]
pw_file = os.environ.get("PW_FILE", "/run/secrets/coti_sk.pw")
# choose behavior by environment
st = SecretType.for_environment(os.getenv("ENVIRONMENT", "prod"), pw_file=pw_file)
secret_bytes = SecretManager.get(encrypted_file, secret_type=st)
# secret_bytes are now decrypted in memory
# pw_file is shredded+deleted (best-effort) after attemptSecretManager.get():
- waits until the password source is available (pw-file strategy)
- decrypts into memory
- deletes the pw file (best-effort shred + unlink)
- Encrypted secret JSON lives on a volume:
./secrets/my-secret.json - Password is injected at startup into container tmpfs:
/run/secrets/coti_sk.pw SecretType.for_environment("prod")expects the password file (waits until it appears)
Pros: password is never stored on disk or in shared volumes.
In dev you may prefer a password in an env var:
export COTI_SK_PW="dev-password"
export ENVIRONMENT="dev"In code:
from coti_secret import SecretManager, SecretType
st = SecretType.for_environment("dev", pw_env_var="COTI_SK_PW", delete_env_var_after_read=True)
secret = SecretManager.get("./secrets/my-secret.json", secret_type=st)Warning: env vars are easier to leak into logs/process inspection and are generally weaker than tmpfs password injection.
A strong next step for high-security production is to avoid password-based decryption entirely and move to an enclave-backed approach such as:
- decrypting inside a hardware-backed environment (enclave/TEE)
- using a cloud KMS with envelope encryption
- using HSMs / hardware tokens so raw private keys never exist in service memory
This changes the model from “decrypt secret at startup” to “perform crypto operations without exporting the key.”