Docker Integration
Arcan integrates with Docker Compose and Docker Swarm to inject secrets into containerized workloads.
Docker Compose
Generate .env files from Arcan secrets for use with docker compose up.
Basic workflow
# Store secrets in Arcan
arcan kv set DATABASE_URL "postgres://user:pass@db:5432/app" -r myapp -e prod
arcan kv set REDIS_URL "redis://cache:6379" -r myapp -e prod
# Generate .env file
arcan docker compose -r myapp -e prod
# Start containers
docker compose up -d
Using with docker-compose.yaml
Reference the generated .env file in your compose configuration:
# docker-compose.yaml
services:
api:
image: myapp:latest
env_file:
- .env
ports:
- "8080:8080"
worker:
image: myapp:latest
env_file:
- .env
command: ["worker"]
Per-environment files
arcan docker compose -r myapp -e dev -o .env.dev
arcan docker compose -r myapp -e staging -o .env.staging
arcan docker compose -r myapp -e prod -o .env.prod
JSON format
arcan docker compose -r myapp -e prod --format=json
Piping to other tools
arcan docker compose -r myapp -e prod --stdout | grep DATABASE
Docker Swarm
Sync Arcan secrets to Docker Swarm's built-in secrets store. Swarm secrets are encrypted at rest and only available to services that explicitly reference them.
Sync workflow
# Sync secrets from Arcan to Swarm
arcan docker swarm sync -r myapp -e prod
# Preview what would be synced
arcan docker swarm sync -r myapp -e prod --dry-run
# List managed secrets
arcan docker swarm ls
# Clean up
arcan docker swarm rm -r myapp -e prod
arcan docker swarm rm --all
Using Swarm secrets in services
After syncing, reference the secrets in your docker-compose.yaml (deploy mode):
# docker-compose.yaml (Swarm stack)
services:
api:
image: myapp:latest
secrets:
- arcan_prod_DATABASE_URL
- arcan_prod_API_KEY
environment:
DATABASE_URL_FILE: /run/secrets/arcan_prod_DATABASE_URL
secrets:
arcan_prod_DATABASE_URL:
external: true
arcan_prod_API_KEY:
external: true
Swarm mounts secrets as files at /run/secrets/<name>. Your application reads them from disk.
Custom prefix
By default, secrets are named arcan_<env>_<KEY>. Override with --prefix:
arcan docker swarm sync -r myapp -e prod --prefix="myapp_"
# Creates: myapp_DATABASE_URL, myapp_API_KEY, etc.
arcan docker swarm sync -r myapp -e prod --prefix=""
# Creates: DATABASE_URL, API_KEY (no prefix)
Complete Docker Compose Example
A full docker-compose.yaml for a web application stack using Arcan-generated .env files:
# docker-compose.yaml
services:
api:
image: myapp-api:latest
build: ./api
env_file:
- .env
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
worker:
image: myapp-api:latest
env_file:
- .env
command: ["node", "worker.js"]
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp"]
interval: 5s
timeout: 3s
retries: 5
cache:
image: redis:7-alpine
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
Generate the .env file and start the stack:
# Generate .env from Arcan
arcan docker compose -r myapp -e prod
# Verify the generated file (keys only, values are present)
cat .env
# DATABASE_URL=postgres://myapp:****@db:5432/myapp
# REDIS_URL=redis://:****@cache:6379
# STRIPE_SECRET=sk_live_****
# SENTRY_DSN=https://****@sentry.io/12345
# Start everything
docker compose up -d
# Check that the API can reach the database
docker compose exec api curl -s http://localhost:8080/health
Complete Swarm Service Example
A Swarm stack file that references Arcan-synced secrets:
# stack.yaml (deployed with: docker stack deploy -c stack.yaml myapp)
version: "3.9"
services:
api:
image: registry.example.com/myapp-api:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
secrets:
- source: arcan_prod_DATABASE_URL
target: database_url
- source: arcan_prod_REDIS_URL
target: redis_url
- source: arcan_prod_STRIPE_SECRET
target: stripe_secret
environment:
# Tell the app where to find secrets on disk
DATABASE_URL_FILE: /run/secrets/database_url
REDIS_URL_FILE: /run/secrets/redis_url
STRIPE_SECRET_FILE: /run/secrets/stripe_secret
ports:
- "8080:8080"
secrets:
arcan_prod_DATABASE_URL:
external: true
arcan_prod_REDIS_URL:
external: true
arcan_prod_STRIPE_SECRET:
external: true
Sync secrets from Arcan and deploy:
# Preview what will be synced
arcan docker swarm sync -r myapp -e prod --dry-run
# Would create: arcan_prod_DATABASE_URL
# Would create: arcan_prod_REDIS_URL
# Would create: arcan_prod_STRIPE_SECRET
# Sync secrets to Swarm
arcan docker swarm sync -r myapp -e prod
# Deploy the stack
docker stack deploy -c stack.yaml myapp
# Verify secrets are mounted
docker exec $(docker ps -q -f name=myapp_api) ls /run/secrets/
# database_url
# redis_url
# stripe_secret
Your application reads secrets from files instead of environment variables:
// Read secret from Swarm-mounted file
func readSecret(name string) string {
// Check for _FILE variant first (Swarm pattern)
filePath := os.Getenv(name + "_FILE")
if filePath != "" {
data, err := os.ReadFile(filePath)
if err == nil {
return strings.TrimSpace(string(data))
}
}
// Fall back to direct environment variable
return os.Getenv(name)
}
When to Use Each Approach
| Approach | Best for | Secret storage | Rotation |
|---|---|---|---|
Compose .env | Local development, single-host staging | Plaintext file on disk (0600 perms) | Re-run arcan docker compose, then docker compose up -d |
| Swarm secrets | Production multi-node clusters | Encrypted in Swarm Raft log, mounted as tmpfs | Re-run arcan docker swarm sync, then rolling update |
| Sidecar | Kubernetes, ECS, any orchestrator | In-memory only, never on disk | Sidecar polls Arcan, injects fresh values |
Compose .env is simplest but stores secrets on disk. Use it for development and simple staging deployments.
Swarm secrets are encrypted at rest and only decrypted in memory on nodes running the service. Use them for production Swarm deployments.
Sidecar (see Sidecar docs) keeps secrets entirely in memory and supports automatic refresh. Use it when you need the strongest security posture or are running on Kubernetes/ECS.
Complete Workflow: Development to Production
This section walks through using Arcan with Docker Compose from initial setup to running your application.
1. Store your secrets in Arcan
# Store production secrets
arcan kv set DATABASE_URL "postgres://myapp:s3cure@db.example.com:5432/myapp" -r myapp -e prod
arcan kv set REDIS_URL "redis://:r3d1s@cache.example.com:6379" -r myapp -e prod
arcan kv set STRIPE_SECRET "sk_live_abc123def456" -r myapp -e prod
arcan kv set SENTRY_DSN "https://abc123@o12345.ingest.sentry.io/67890" -r myapp -e prod
arcan kv set JWT_SECRET "your-256-bit-jwt-signing-key" -r myapp -e prod
# Store development secrets (different values)
arcan kv set DATABASE_URL "postgres://dev:dev@localhost:5432/myapp_dev" -r myapp -e dev
arcan kv set REDIS_URL "redis://localhost:6379" -r myapp -e dev
2. Generate the .env file
# For development
arcan docker compose -r myapp -e dev -o .env
# Verify (keys shown, values present)
cat .env
# DATABASE_URL=postgres://dev:dev@localhost:5432/myapp_dev
# REDIS_URL=redis://localhost:6379
3. Use the complete docker-compose.yaml
# docker-compose.yaml
services:
api:
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
worker:
build:
context: .
dockerfile: Dockerfile
env_file:
- .env
command: ["node", "worker.js"]
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp_dev
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev -d myapp_dev"]
interval: 5s
timeout: 3s
retries: 5
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:
redisdata:
4. Start and verify
# Generate fresh .env and start
arcan docker compose -r myapp -e dev
docker compose up -d
# Check all services are healthy
docker compose ps
# NAME SERVICE STATUS
# api api running (healthy)
# worker worker running
# db db running (healthy)
# cache cache running (healthy)
# Test the API
curl http://localhost:3000/health
# {"status":"ok","db":"connected","cache":"connected"}
5. Rotate a secret
# Update the secret in Arcan
arcan kv set DATABASE_URL "postgres://myapp:n3wpassword@db.example.com:5432/myapp" -r myapp -e prod
# Regenerate .env and restart
arcan docker compose -r myapp -e prod -o .env
docker compose up -d
Choosing the Right Approach
Compose .env | Swarm Secrets | Sidecar | |
|---|---|---|---|
| Use when | Local dev, single-host staging, CI/CD | Production Swarm clusters | Kubernetes, ECS, any orchestrator |
| Secrets stored | Plaintext file on disk (0600 perms) | Encrypted in Swarm Raft log, mounted as tmpfs | In-memory only, never on disk |
| Rotation | Re-run arcan docker compose, then docker compose up -d | Re-run arcan docker swarm sync, then rolling update | Sidecar polls Arcan, injects fresh values automatically |
| Security level | Basic -- file on disk | Good -- encrypted at rest, tmpfs in container | Best -- never persisted, auto-refreshes |
| Setup complexity | Minimal | Moderate (Swarm init, service configs) | Higher (sidecar container, shared volume) |
| Best for | Getting started fast, development | Teams already on Docker Swarm | Production workloads needing strongest security |
Rule of thumb:
- Development: Compose
.env(simple, fast) - Staging on Swarm: Swarm secrets (encrypted, production-like)
- Production on Kubernetes/ECS: Sidecar (see Sidecar docs)
Security Notes
.envfiles are written with0600permissions (owner read/write only)- Add
.env*to your.gitignoreto prevent accidental commits - Values containing spaces, quotes, or shell metacharacters are automatically quoted
- Swarm secrets are labeled with
managed-by=arcan,arcan-realm, andarcan-envfor tracking - Swarm secrets are immutable; updates remove and recreate the secret