Infisical End to End: Self-Hosting, the CLI, Docker Compose, and Kubernetes
Most projects start with a .env file, and most projects never leave it. The file gets copied to a server over scp, pasted into a Slack thread when a teammate joins, and forgotten on three laptops. Nobody knows which values are current, and rotating a leaked key means hunting down every copy.
This post walks through replacing that with Infisical — self-hosted, so the secrets stay on your own machine — and then actually wiring it into the two places secrets are usually consumed: a Docker Compose host and a Kubernetes cluster.
Path through this post:
- What Infisical is
- Pick an install path
- Self-host with Docker Compose
- Install the CLI
- Bootstrap a project from a local
.env - Use the secrets on a Compose server — replacing the server-side
.env - Use the secrets in Kubernetes
Versions referenced below: Infisical server
infisical/infisical:lateston the publisheddocker-compose.prod.yml, and the Kubernetes operator Helm chartv0.11.9with thev1beta1CRDs. Infisical moves fast — check the version numbers against the docs before pasting.
1. What is Infisical?
Infisical is an open-source secret management platform. At its core it is a server that stores secrets encrypted at rest, plus a set of clients — a CLI, SDKs, a Kubernetes operator, CI integrations — that fetch those secrets at runtime so they never have to live in a file next to your code.
The mental model is a three-level tree:
Organization
└── Project (e.g. "orders-api")
├── Environment: dev
├── Environment: staging
└── Environment: prod
└── Folder path: / /database /stripe
└── SECRET_KEY = value
A secret is addressed by project + environment slug + folder path + key. Every client you will see below — infisical run, the operator CRD, the SDKs — takes some version of those four coordinates.
What you actually get over a .env file
Problem with .env | What Infisical does |
|---|---|
| No idea who has a copy | Access control per project and environment, plus an audit log of every read |
| Rotation means chasing files | Change the value once; clients pick it up on their next fetch or restart |
| Secrets end up in git | Nothing to commit — the project config file that is committed holds no values |
| Machines need a human to hand them a file | Machine identities authenticate on their own with a client ID and secret |
| Dev and prod values drift | Same key, different environment — one place to compare them |
It also does certificate management, dynamic secrets (short-lived database credentials minted on demand), and secret scanning. This post sticks to static secrets, which is what most teams need first.
How it compares
- HashiCorp Vault — far more powerful, far more operational surface. If you are not already running Vault and you mostly want “a good place to keep API keys,” Vault is a lot of machine to adopt.
- Doppler / AWS Secrets Manager / GCP Secret Manager — comparable ergonomics, but hosted only. Infisical’s differentiator here is that the same product self-hosts cleanly.
- SOPS + age — excellent if you want secrets in git, encrypted. Different philosophy: SOPS has no server, no audit log, and no runtime API. It is a good fit for GitOps, a poor fit for “rotate this key across 12 services right now.”
2. Pick an install path
There are two decisions, and only the second one is interesting.
Server: Infisical Cloud (https://app.infisical.com, free tier available) or self-hosted. Everything in this post works with either — the only difference is that self-hosted clients need to be told where the server is. Sections 3 onward assume self-hosted.
Self-hosting method:
| Method | Good for | Trade-off |
|---|---|---|
| Docker Compose | A single host, homelab, small team | Single node, no HA — the documented starting point |
| Kubernetes (Helm) | You already run Kubernetes | More moving parts; Postgres and Redis are on you |
| Standalone Docker | Embedding in an existing orchestrator | You supply Postgres and Redis yourself |
Docker Compose is what section 3 covers. Note the distinction that trips people up: self-hosting Infisical on Compose (section 3) and consuming Infisical secrets from a Compose app (section 6) are unrelated problems, and your Infisical server does not have to live on the same host as the apps it serves.
Prerequisites
- A host with Docker and the Compose plugin (
docker compose versionworks) - ~2 GB RAM free; the stack is a Node backend, Postgres, and Redis
- A DNS name and TLS if anything other than your laptop will use it
3. Self-host with Docker Compose
3.1 Fetch the files
mkdir -p ~/infisical && cd ~/infisical
curl -o docker-compose.prod.yml \
https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
The Compose file defines three services — backend (infisical/infisical:latest), db (postgres:14-alpine), and redis — with named volumes pg_data and redis_data, and publishes the backend as 80:8080.
3.2 Generate real keys
The example .env ships with sample values that are explicitly marked as development-only. Replace them before anything else:
echo "ENCRYPTION_KEY=$(openssl rand -hex 16)"
echo "AUTH_SECRET=$(openssl rand -base64 32)"
Two keys, two different jobs:
| Variable | Job | Format |
|---|---|---|
ENCRYPTION_KEY | Encrypts every secret at rest in Postgres | 32 hex chars (rand -hex 16) |
AUTH_SECRET | Signs session and JWT tokens | base64, 32 bytes |
Back up
ENCRYPTION_KEYnow, outside this host. A Postgres dump without it is unreadable. Restoring the database onto a new machine with a freshly generated key gets you a working login page and zero recoverable secrets. Rotating it later is a documented but deliberate procedure — not something to improvise during an incident.
3.3 Fill in the rest of .env
# --- required ---
ENCRYPTION_KEY=<the 32 hex chars from above>
AUTH_SECRET=<the base64 string from above>
POSTGRES_USER=infisical
POSTGRES_PASSWORD=<a long random password>
POSTGRES_DB=infisical
DB_CONNECTION_URI=postgres://infisical:<same password>@db:5432/infisical
REDIS_URL=redis://redis:6379
# The URL users and clients will actually hit. Not "localhost" if it's a server.
SITE_URL=https://secrets.example.com
# --- optional but recommended: SMTP, for invites and password resets ---
SMTP_HOST=
SMTP_PORT=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=
SMTP_FROM_NAME=Infisical
Two things worth getting right the first time:
DB_CONNECTION_URImust matchPOSTGRES_*. The hostname isdb— the Compose service name — notlocalhost.SITE_URLmust be the externally reachable URL. It ends up in invite links and email, and a wrong value produces links nobody can click.
Lock the file down, since it now holds the key to everything:
chmod 600 .env
3.4 Start it
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml ps
Checkpoint: all three containers are running, and db reports healthy. If backend restarts in a loop, it is almost always DB_CONNECTION_URI:
docker compose -f docker-compose.prod.yml logs -f backend
3.5 First login
Open http://<host>/ — the backend is published on port 80. The first account to sign up becomes the instance admin, so do this immediately after the stack comes up and before the host is reachable from anywhere you don’t control.
Then: create an organization, create your first project, and note the project ID from the project’s settings page. Every machine client below needs it.
3.6 Put TLS in front of it
The Compose stack speaks plain HTTP. Anything beyond a laptop needs a reverse proxy — Caddy, nginx, or Cloudflare Tunnel — terminating TLS and forwarding to port 80 on the host. Set SITE_URL to the https:// address and restart the backend afterward.
4. Install the CLI
The CLI is the tool you will use most: it bootstraps projects, injects secrets into processes, and authenticates machines.
# macOS
brew install infisical/get-cli/infisical
# Debian / Ubuntu
curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | sudo -E bash
sudo apt-get update && sudo apt-get install -y infisical
# RHEL / CentOS / Amazon Linux
curl -1sLf 'https://artifacts-cli.infisical.com/setup.rpm.sh' | sudo -E bash
sudo yum install infisical
# Any platform with Node
npm install -g @infisical/cli
infisical --version
On servers and in CI, pin the version rather than tracking latest, so a reinstall doesn’t silently change behavior.
Pointing the CLI at your own server
This is the single most common stumbling block with a self-hosted instance: the CLI defaults to Infisical Cloud. If you skip this step, infisical login will cheerfully authenticate you against app.infisical.com and then report that your project does not exist.
Resolution order, highest precedence first:
| Where | Example |
|---|---|
--domain flag | infisical login --domain="https://secrets.example.com" |
INFISICAL_DOMAIN env var | export INFISICAL_DOMAIN=https://secrets.example.com |
domain field in .infisical.json | pins the instance per project |
| Default | https://app.infisical.com |
(The older INFISICAL_API_URL is still honored; INFISICAL_DOMAIN wins when both are set.)
Use the base URL — no /api suffix. The simplest thing is to export it once in your shell profile:
export INFISICAL_DOMAIN="https://secrets.example.com"
infisical login
For a human on a laptop, infisical login opens a browser and stores the credential in the OS keychain. Machines use a different mechanism — section 6.
5. Bootstrap a project from a local .env
You have an existing project with a .env full of values. The goal: get them into Infisical without typing each one.
5.1 Link the directory to a project
From your project root:
cd ~/code/orders-api
infisical init
This writes .infisical.json:
{
"workspaceId": "63ee5410a45f7a1ed39ba118",
"defaultEnvironment": "dev",
"domain": "https://secrets.example.com"
}
Commit this file. It contains no secrets — only which project and instance this directory belongs to. Adding the domain field means teammates don’t each have to export INFISICAL_DOMAIN.
While you are here, make sure the actual secrets can’t follow it into git:
grep -qxF '.env' .gitignore || echo '.env' >> .gitignore
5.2 Push the existing .env up
infisical secrets set takes any number of KEY=value pairs, so a whole file goes up in one call:
#!/usr/bin/env bash
# import-env.sh — push a local dotenv file into an Infisical environment
set -euo pipefail
ENV_FILE="${1:-.env}"
ENV_SLUG="${2:-dev}"
SECRET_PATH="${3:-/}"
[[ -f "$ENV_FILE" ]] || { echo "no such file: $ENV_FILE" >&2; exit 1; }
# Keep KEY=value lines; drop comments, blanks, and any `export ` prefix.
mapfile -t PAIRS < <(
sed -e 's/^[[:space:]]*export[[:space:]]\+//' "$ENV_FILE" \
| grep -E '^[A-Za-z_][A-Za-z0-9_]*=' \
| sed -e 's/=[[:space:]]*"\(.*\)"[[:space:]]*$/=\1/' \
-e "s/=[[:space:]]*'\(.*\)'[[:space:]]*\$/=\1/"
)
(( ${#PAIRS[@]} )) || { echo "no KEY=value lines found in $ENV_FILE" >&2; exit 1; }
echo "Importing ${#PAIRS[@]} secrets into env=$ENV_SLUG path=$SECRET_PATH"
printf ' %s\n' "${PAIRS[@]%%=*}"
infisical secrets set "${PAIRS[@]}" --env="$ENV_SLUG" --path="$SECRET_PATH"
chmod +x import-env.sh
./import-env.sh .env dev /
./import-env.sh .env.production prod /
The script strips export prefixes and surrounding quotes, which is where naive one-liners usually go wrong. It intentionally does not handle multi-line values — see the note below.
Checkpoint:
infisical secrets --env=dev
5.3 Things to know while importing
Multi-line values and file contents (PEM keys, service-account JSON) should be read from the file rather than pasted:
infisical secrets set PRIVATE_KEY=@./private.pem
infisical secrets set GCP_SA_JSON=@./service-account.json
A literal @ at the start of a value has to be escaped: infisical secrets set EMAIL="\@example.com".
Organize with folders rather than one flat list, once you pass twenty or so keys:
infisical secrets folders create --name=database --env=dev
infisical secrets set DB_PASSWORD=... --env=dev --path="/database"
Use --tag to mark subsets you will want to fetch separately later:
infisical secrets set STRIPE_KEY=... --tag=payments --env=prod
5.4 Run locally without a .env at all
This is the payoff for developers:
infisical run --env=dev -- npm run dev
infisical run fetches the secrets, injects them as environment variables into the child process, and never writes them to disk. Useful flags:
| Flag | Effect |
|---|---|
--env | Environment slug; defaults to dev |
--path | Folder to pull from; repeatable (--path=/common --path=/api) |
--recursive | Include subfolders under --path |
--watch | Restart the command when a secret changes upstream |
--command | Run a shell string instead of an argv (--command="npm run build && npm start") |
--projectId | Required when authenticating as a machine identity rather than a user |
Once this works, delete the local .env. That is the whole point.
6. Use the secrets on a Compose server
Now the server side. You have a host running docker compose up -d against a .env file that you copied there by hand, and you want that file gone.
6.1 First: a machine identity
A server cannot do a browser login. It authenticates as a machine identity using Universal Auth — a client ID and client secret pair.
In the UI: Organization → Access Control → Machine Identities → Create. Give it a name (orders-api-prod), then Add Client Secret and copy both values — the secret is shown once.
Then grant it access to the project: Project → Access Control → Machine Identities → Add, with a read-only role. A deploy host has no business writing secrets.
One identity per service per environment. Shared credentials are how a staging compromise becomes a production one.
On the server:
export INFISICAL_DOMAIN="https://secrets.example.com"
export INFISICAL_TOKEN=$(infisical login \
--method=universal-auth \
--client-id="<client-id>" \
--client-secret="<client-secret>" \
--plain --silent)
--plain --silent prints just the JWT, which makes it usable in a $(...) like this. The token is short-lived (30 days by default) and re-minted from the client ID/secret whenever you need a new one.
Now pick one of three patterns.
6.2 Pattern A — generate .env at deploy time
The smallest change to an existing setup: keep the .env file, but stop maintaining it by hand.
#!/usr/bin/env bash
# deploy.sh
set -euo pipefail
umask 077 # so the .env we write is 600, not 644
export INFISICAL_DOMAIN="https://secrets.example.com"
PROJECT_ID="<your-project-id>"
INFISICAL_TOKEN=$(infisical login \
--method=universal-auth \
--client-id="$UA_CLIENT_ID" \
--client-secret="$UA_CLIENT_SECRET" \
--plain --silent)
export INFISICAL_TOKEN
infisical export \
--projectId="$PROJECT_ID" \
--env=prod \
--path=/ \
--format=dotenv > .env
docker compose up -d
--format accepts dotenv, dotenv-export, json, yaml, and csv.
What this buys you: one source of truth, and rotation is a redeploy instead of an scp.
What it doesn’t: the plaintext file is still on disk, and Compose’s .env also feeds ${VAR} interpolation in the Compose file itself — so a secret named POSTGRES_PASSWORD will silently substitute into your Compose file too, whether or not you meant it to. Honest assessment: this is a real improvement over a hand-copied file, but it is the weakest of the three.
You still need the client ID and secret on the host. Put them in a root-owned /etc/infisical.env with mode 600, or in the systemd unit’s EnvironmentFile. There is always one bootstrap credential; the goal is to have exactly one, and to have it be revocable from a UI.
6.3 Pattern B — never write the file
infisical run can wrap the Compose CLI itself:
infisical run \
--projectId="<your-project-id>" \
--env=prod \
-- docker compose up -d
The secrets land in the environment of the docker compose process. Compose then uses them for two things:
1. ${VAR} interpolation in the Compose file:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
2. Passthrough entries — a key with no value is taken from the shell:
services:
api:
image: ghcr.io/example/orders-api:1.4.2
environment:
- DATABASE_URL
- STRIPE_SECRET_KEY
- JWT_SIGNING_KEY
That second form is the one to reach for. It names exactly which secrets reach which container, and it reads as documentation.
Two gotchas:
- Shell environment beats the
.envfile for interpolation, so during a migration a leftover.envwon’t override whatinfisical runinjects — but it also won’t be obviously unused. Delete it once you’ve cut over. docker compose up -dreturns immediately, and containers keep the environment they were started with. Secrets change → you must re-run the command. A rotation is not picked up by running containers on its own.
6.4 Pattern C — the CLI inside the container
Highest isolation: each container fetches its own secrets at startup, so nothing on the host ever holds the full set.
FROM node:22-alpine
RUN apk add --no-cache curl \
&& curl -1sLf 'https://artifacts-cli.infisical.com/setup.apk.sh' | sh \
&& apk add --no-cache infisical
WORKDIR /app
COPY . .
RUN npm ci --omit=dev
ENTRYPOINT ["infisical", "run", "--projectId=<your-project-id>", "--env=prod", "--"]
CMD ["node", "server.js"]
services:
orders-api:
build: .
environment:
INFISICAL_TOKEN: ${INFISICAL_TOKEN}
INFISICAL_DOMAIN: ${INFISICAL_DOMAIN}
export INFISICAL_DOMAIN="https://secrets.example.com"
export INFISICAL_TOKEN=$(infisical login --method=universal-auth \
--client-id="$UA_CLIENT_ID" --client-secret="$UA_CLIENT_SECRET" --plain --silent)
docker compose up -d --build
Do not set
INFISICAL_DOMAINto alocalhostURL here. Inside the container that resolves to the container itself. If Infisical is running on the same host, usehost.docker.internal(or put both stacks on a shared Docker network and use the service name).
With multiple services on different permissions, give each its own identity and its own variable — INFISICAL_TOKEN_WEB, INFISICAL_TOKEN_API — and map them per service.
6.5 Which pattern
A: export to .env | B: infisical run -- | C: CLI in container | |
|---|---|---|---|
| Plaintext on host disk | Yes | No | No |
| Change to app image | None | None | Dockerfile + CLI |
| Per-service scoping | No | Manual, per key | Yes, per identity |
Works with ${VAR} interpolation | Yes | Yes | No |
| Good for | Migrating an existing host | Most Compose setups | Multi-tenant hosts |
Start at B. Go to C when services need different permissions. Use A only when something in your stack genuinely requires a file.
7. Use the secrets in Kubernetes
In Kubernetes you don’t inject secrets into processes — you let an operator sync Infisical secrets into native Kubernetes Secret objects, and consume those the way you already do.
7.1 Install the operator
helm repo add infisical-helm-charts \
'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
helm repo update
helm install infisical-secrets-operator \
infisical-helm-charts/secrets-operator \
--namespace infisical-operator-system \
--create-namespace
kubectl get pods -n infisical-operator-system
kubectl get crds | grep infisical
The current API is secrets.infisical.com/v1beta1, with three CRDs that split responsibilities cleanly:
| CRD | Answers |
|---|---|
InfisicalConnection | Which Infisical instance, and how to trust its TLS |
InfisicalAuth | Which machine identity, and which auth method |
InfisicalStaticSecret | Which secrets sync where, and how often |
The older v1alpha1 InfisicalSecret — one resource with connection, auth, and sync inlined — is deprecated. InfisicalPushSecret and InfisicalDynamicSecret still live under v1alpha1.
7.2 The credentials Secret
There is a chicken-and-egg problem here: the operator needs a credential to fetch credentials. Create a machine identity as in 6.1, then:
kubectl create secret generic universal-auth-credentials \
--namespace=orders \
--from-literal=clientId="<client-id>" \
--from-literal=clientSecret="<client-secret>"
7.3 The three resources
# 1. Where the Infisical instance lives
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalConnection
metadata:
name: self-hosted-infisical
namespace: orders
spec:
address: https://secrets.example.com
---
# 2. How to authenticate to it
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalAuth
metadata:
name: orders-auth
namespace: orders
spec:
infisicalConnectionRef:
name: self-hosted-infisical
namespace: orders
method: universal
universal:
clientIdRef:
name: universal-auth-credentials
namespace: orders
key: clientId
clientSecretRef:
name: universal-auth-credentials
namespace: orders
key: clientSecret
---
# 3. What to sync, and where to put it
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalStaticSecret
metadata:
name: orders-api-secrets
namespace: orders
spec:
infisicalAuthRef:
name: orders-auth
namespace: orders
syncOptions:
refreshInterval: 60s
sources:
- projectId: <your-project-id>
environmentSlug: prod
secretPath: /
targets:
- name: orders-api-secrets
namespace: orders
kind: Secret
creationPolicy: Owner
kubectl apply -f infisical.yaml
Checkpoint:
kubectl get infisicalconnection,infisicalauth,infisicalstaticsecret -n orders
kubectl get secret orders-api-secrets -n orders -o jsonpath='{.data}' | jq 'keys'
The printer columns show Ready on the connection and auth, and Synced on the static secret. When something is wrong, kubectl describe infisicalstaticsecret orders-api-secrets -n orders puts the reason in the conditions.
Use address: https://secrets.example.com without a /api suffix — the operator appends it. For an instance behind a private CA, add TLS settings under spec.tls on the connection rather than disabling verification.
7.4 Field notes on the spec
sources is a list, so one target can merge several places — shared config plus service-specific keys:
sources:
- projectId: <project-id>
environmentSlug: prod
secretPath: /common
- projectId: <project-id>
environmentSlug: prod
secretPath: /orders
recursive: true
tagSlugs: ["payments"]
Each source takes either projectId or projectSlug, plus environmentSlug and secretPath; recursive pulls subfolders, and tagSlugs filters.
targets is also a list, and can write a ConfigMap instead of a Secret (kind: ConfigMap — for non-sensitive config only). creationPolicy matters:
creationPolicy | Behavior |
|---|---|
Owner | Operator owns the Secret; deleting the CR garbage-collects it. Default choice. |
Orphan | The Secret outlives the CR. For migrations and for secrets other controllers touch. |
syncOptions.refreshInterval is the poll period (60s is a sane default). instantUpdates: true opts into push-based updates so a rotation propagates without waiting out the interval.
targets[].template reshapes the output when your app wants a specific format rather than one key per secret — a rendered config file, a DATABASE_URL assembled from parts. Set engineVersion: v1 and provide Go templates under data.
7.5 Consume it, and reload on change
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
namespace: orders
annotations:
secrets.infisical.com/auto-reload: "true"
spec:
replicas: 3
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
spec:
containers:
- name: api
image: ghcr.io/example/orders-api:1.4.2
envFrom:
- secretRef:
name: orders-api-secrets
The secrets.infisical.com/auto-reload: "true" annotation is the part people miss. Environment variables are read once at process start, so without it a rotated secret sits in the Kubernetes Secret while every running pod keeps using the old value until someone happens to redeploy. With it, the operator rolls the Deployment when the synced Secret changes.
7.6 In-cluster auth without static credentials
Universal Auth means a client ID and secret sitting in a Kubernetes Secret — better than a .env, but still a long-lived credential you have to rotate.
If your cluster can do better, InfisicalAuth supports other methods, each keyed on a workload identity the platform already vouches for:
method | Uses |
|---|---|
kubernetes | A ServiceAccount token from this cluster |
awsIam | The node/pod IAM role |
gcpIdToken | Workload Identity |
azure | Azure managed identity |
ldap | LDAP bind |
method: kubernetes is the natural fit for a self-hosted instance running next to the cluster — no static secret to rotate, and identity is scoped to a ServiceAccount:
spec:
method: kubernetes
kubernetes:
identityIdRef:
name: infisical-identity-id
namespace: orders
key: identityId
serviceAccountRef:
name: orders-api
namespace: orders
Worth the extra setup for production. Start with universal auth to prove the pipeline works, then switch.
7.7 One thing the operator does not solve
The synced object is an ordinary Kubernetes Secret — base64, not encryption. Anyone with get secrets in that namespace can read it. Infisical fixes the distribution problem, not Kubernetes’ storage model. Still worth doing: encryption at rest on etcd, and RBAC that doesn’t hand out namespace-wide secret reads.
Where this leaves you
| Context | Mechanism | Secrets on disk |
|---|---|---|
| Local development | infisical run -- npm run dev | None |
| Compose server | infisical run -- docker compose up -d | None |
| Kubernetes | Operator → native Secret → envFrom | Cluster only |
| CI | Machine identity + INFISICAL_TOKEN | None |
The through-line is that every client authenticates as itself and fetches what it is allowed to fetch. Rotating a key becomes one edit in one UI, and the blast radius of a leaked credential is one identity you can revoke.
The honest caveats: you are now running a stateful service that everything else depends on, so its Postgres needs a backup strategy and its uptime is your uptime. ENCRYPTION_KEY is a single point of total data loss — treat the backup of that one string as seriously as the database itself. And in Kubernetes, secrets still land in etcd as base64. Infisical raises the floor considerably; it does not remove the need to think.
References
- Infisical documentation
- Self-hosting with Docker Compose
- CLI overview and installation
infisical run·infisical secrets·infisical export- Project config file (
.infisical.json) - Machine identities: Universal Auth
- Inject secrets into a Docker application
- Kubernetes Operator · operator source and
v1beta1examples