Docker Compose Deployment¶
Deploy EUDIPLO using Docker Compose for local development, testing, and small-scale production environments. Docker Compose provides an easy way to run multi-container applications with minimal configuration.
Overview¶
EUDIPLO offers three Docker Compose deployment options:
| Deployment | Use Case | Database | Key Storage | Production Ready |
|---|---|---|---|---|
| Quick Start | Quick testing, demos | None (ephemeral) | Ephemeral | ❌ No |
| Minimal | Development, testing | SQLite | Filesystem | ⚠️ Limited |
| Full | Production, staging | PostgreSQL | Vault | ✅ Yes |
Prerequisites¶
Before you begin, ensure you have:
- Docker installed (Get Docker)
- Docker Compose V2 or later (included with Docker Desktop)
- Basic understanding of Docker concepts
Verify installation:
Quick Start (Root Directory)¶
The fastest way to get EUDIPLO running for quick testing.
Setup¶
From the repository root, create an .env file with required credentials:
cp .env.example .env
# Generate required credentials
echo "MASTER_SECRET=$(openssl rand -base64 32)" >> .env
echo "AUTH_CLIENT_ID=demo" >> .env
echo "AUTH_CLIENT_SECRET=demo-secret" >> .env
Start Services¶
This starts:
- Backend API on port 3000
- Client UI on port 4200
Access Applications¶
- Backend API: http://localhost:3000/api
- Backend Health: http://localhost:3000/health
- Client UI: http://localhost:4200
Stop Services¶
Ephemeral Storage
This deployment uses default in-memory storage. All data is lost when containers stop. Use Minimal or Full deployment for persistent storage.
Minimal Deployment¶
Ideal for development and testing with persistent storage but without production dependencies.
Features¶
- ✅ Persistent Storage - SQLite database
- ✅ Filesystem Keys - Keys stored on local filesystem
- ✅ Simple Setup - No external dependencies
- ✅ Fast Startup - Minimal overhead
- ⚠️ Limited Scalability - Not suitable for high-traffic production
Setup¶
Navigate to minimal deployment:
Copy and configure environment:
Edit .env:
# Public URL (for OAuth redirects)
PUBLIC_URL=http://localhost:3000
# Environment
NODE_ENV=development
Deploy¶
Start services:
Watch logs:
Access Applications¶
- Backend API: http://localhost:3000/api
- Client UI: http://localhost:4200
Data Persistence¶
Data is stored in Docker volumes:
# View volumes
docker volume ls | grep minimal
# Inspect volume
docker volume inspect minimal_eudiplo-config
Stop and Cleanup¶
Full Deployment (Production)¶
Production-ready deployment with PostgreSQL, HashiCorp Vault, and MinIO object storage.
Features¶
- ✅ PostgreSQL Database - Relational database for transactional data
- ✅ HashiCorp Vault - Secure key management
- ✅ MinIO - S3-compatible object storage
- ✅ Health Checks - Automatic restart on failure
- ✅ Docker Networks - Isolated networking
- ✅ Persistent Volumes - Data survives restarts
Architecture¶
┌─────────────────┐
│ Client (4200) │
└────────┬────────┘
│
┌────────▼────────┐ ┌──────────────┐
│ Backend (3000) │◄────►│ PostgreSQL │
└────────┬────────┘ └──────────────┘
│
├──────────────►┌──────────────┐
│ │ Vault (8200) │
│ └──────────────┘
│
└──────────────►┌──────────────┐
│ MinIO (9000) │
└──────────────┘
Setup¶
Navigate to full deployment:
Copy and configure environment:
Edit .env with your configuration:
# Public URL (change for production)
PUBLIC_URL=https://your-domain.com
# Environment
NODE_ENV=production
# PostgreSQL Configuration
DB_TYPE=postgres
DB_HOST=database
DB_PORT=5432
DB_USERNAME=eudiplo_user
DB_PASSWORD=strong-secure-password-here
DB_DATABASE=eudiplo
# HashiCorp Vault
VAULT_TOKEN=your-vault-root-token
VAULT_ADDR=http://vault:8200
# MinIO (S3-compatible storage)
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin-secure-password
# Application Secrets
MASTER_SECRET=your-secret-jwt-key-change-in-production
AUTH_CLIENT_ID=your-client-id
AUTH_CLIENT_SECRET=your-client-secret
# Metrics Protection (optional but recommended)
METRICS_TOKEN=your-metrics-bearer-token
# Logging
LOG_LEVEL=info
Security Warning
Never use default credentials in production! Change all passwords, tokens, and secrets before deploying.
Deploy¶
Start all services:
Watch startup logs:
Wait for all services to be healthy:
Expected output:
NAME STATUS PORTS
full-database-1 Up (healthy) 5432/tcp
full-vault-1 Up 0.0.0.0:8200->8200/tcp
full-minio-1 Up (healthy) 0.0.0.0:9000-9001->9000-9001/tcp
full-eudiplo-1 Up (healthy) 0.0.0.0:3000->3000/tcp
full-eudiplo-client-1 Up (healthy) 0.0.0.0:4200->80/tcp
Access Applications¶
- Backend API: http://localhost:3000/api
- Backend Health: http://localhost:3000/health
- Client UI: http://localhost:4200
- Vault UI: http://localhost:8200 (Token: from VAULT_TOKEN)
- MinIO Console: http://localhost:9001 (Login: MINIO_ROOT_USER/PASSWORD)
Service Management¶
View running services:
View logs:
# All services
docker compose logs -f
# Specific service
docker compose logs -f eudiplo
docker compose logs -f database
Restart a service:
Stop services:
Remove all data:
Testing & Verification¶
Health Checks¶
Verify backend health:
Expected response:
Database Connection¶
Connect to PostgreSQL (full deployment):
Inside psql:
Vault Access¶
Access Vault UI:
- Navigate to http://localhost:8200
- Sign in with token from
.env(VAULT_TOKEN) - Verify EUDIPLO secrets are stored
Via CLI:
MinIO Verification¶
- Navigate to http://localhost:9001
- Login with credentials from
.env - Verify buckets are created
Via CLI:
Troubleshooting¶
Services Not Starting¶
Check service status:
View logs for failed service:
Common issues:
-
Port already in use:
-
Database not ready:
-
Permission errors:
Connection Refused Errors¶
If backend can't connect to database:
# Verify database is running
docker compose ps database
# Check database logs
docker compose logs database
# Verify network connectivity
docker compose exec eudiplo ping database
Data Persistence Issues¶
Verify volumes are created:
Vault Unsealing¶
If Vault is sealed:
# Check status
docker compose exec vault vault status
# In dev mode, Vault auto-unseals
# For production, follow Vault unsealing procedures
Configuration¶
Environment Variables¶
Common environment variables:
| Variable | Description | Default |
|---|---|---|
PUBLIC_URL |
Public URL for OAuth redirects | http://localhost:3000 |
NODE_ENV |
Environment (development/production) | development |
DB_TYPE |
Database type (postgres/sqlite) | postgres |
DB_HOST |
Database hostname | database |
DB_PORT |
Database port | 5432 |
DB_USERNAME |
Database username | - |
DB_PASSWORD |
Database password | - |
DB_DATABASE |
Database name | eudiplo |
VAULT_TOKEN |
Vault root token | - |
VAULT_ADDR |
Vault address | http://vault:8200 |
MASTER_SECRET |
Master secret for JWT and encryption | - (required) |
AUTH_CLIENT_ID |
OAuth client ID | - (required) |
AUTH_CLIENT_SECRET |
OAuth client secret | - (required) |
METRICS_TOKEN |
Bearer token for /metrics endpoint | - (unprotected) |
LOG_LEVEL |
Logging level | info |
See Configuration Documentation for complete list.
Client Environment Variables¶
The client container supports the following environment variables:
| Variable | Description | Default |
|---|---|---|
CLIENT_BASE_HREF |
HTML base href (<base href="..." />). Useful for reverse proxy setups where the client is served from a subpath. For example, if the client is served from https://example.com/eudiplo-client/, set CLIENT_BASE_HREF=/eudiplo-client/. |
/ |
Base Href Normalization
The CLIENT_BASE_HREF value is automatically normalized to ensure it starts and ends with /. For example, eudiplo-client becomes /eudiplo-client/.
Reverse Proxy Setup Required
Setting CLIENT_BASE_HREF alone is not enough — the reverse proxy must also be configured to forward the subpath to the client container. See Serving the Client from a Subpath for complete Nginx and Traefik examples.
services:
eudiplo:
ports:
- '8080:3000' # Change 3000 to 8080
eudiplo-client:
ports:
- '8081:80' # Change 4200 to 8081
Resource Limits¶
Add resource limits to prevent resource exhaustion:
services:
eudiplo:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
Updating¶
Update to Latest Images¶
Pull latest images:
Restart services:
Update to Specific Version¶
Edit docker-compose.yml:
services:
eudiplo:
image: ghcr.io/openwallet-foundation-labs/eudiplo:v1.2.3
eudiplo-client:
image: ghcr.io/openwallet-foundation-labs/eudiplo-client:v1.2.3
Then:
Backup & Restore¶
Backup PostgreSQL Database¶
# Create backup
docker compose exec database pg_dump -U eudiplo_user eudiplo > backup.sql
# Or with Docker
docker compose exec -T database pg_dump -U eudiplo_user eudiplo > backup.sql
Restore PostgreSQL Database¶
# Stop backend
docker compose stop eudiplo
# Restore
cat backup.sql | docker compose exec -T database psql -U eudiplo_user eudiplo
# Start backend
docker compose start eudiplo
Backup Vault Data¶
# Create volume backup
docker run --rm \
-v full_vault_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/vault-backup.tar.gz -C /data .
Backup MinIO Data¶
# Create volume backup
docker run --rm \
-v full_minio_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/minio-backup.tar.gz -C /data .
Monitoring¶
View Resource Usage¶
Check Logs¶
# Follow all logs
docker compose logs -f
# Last 100 lines
docker compose logs --tail=100
# Specific time range
docker compose logs --since 2h
Health Check Status¶
# Check all services health
docker compose ps
# Backend health endpoint
curl http://localhost:3000/health
# Database health
docker compose exec database pg_isready -U eudiplo_user
Production Considerations¶
Security¶
Before deploying to production:
- Change all default credentials (database passwords, Vault token, JWT secret)
- Use HTTPS with valid SSL certificates (not self-signed)
- Enable firewall and restrict port access
- Use secrets management instead of
.envfiles - Regular security updates for all images
- Enable Docker Content Trust for image verification
- Configure
ENCRYPTION_KEY_SOURCE=vaultso the encryption key is only in RAM, not in environment variables (see Encryption at Rest)
Secret Management Strategies¶
EUDIPLO requires several secrets (database credentials, JWT secret, encryption key, etc.). While the quick-start uses .env files, production deployments should use proper secret management.
Credential Categories¶
| Secret | Risk Level | Dev/Test Approach | Production Approach |
|---|---|---|---|
DB_PASSWORD |
High | .env file |
Docker Secrets / Vault Agent |
MASTER_SECRET |
Critical | .env file |
Docker Secrets / Vault Agent |
AUTH_CLIENT_SECRET |
Critical | .env file |
Docker Secrets / Vault Agent |
S3_SECRET_ACCESS_KEY |
High | .env file |
Docker Secrets / IAM Role |
ENCRYPTION_KEY |
Critical | .env file |
Application-level fetch (built-in) |
Option 1: Docker Secrets (Docker Swarm)¶
Docker Secrets provide encrypted secret storage for Swarm mode:
# Create secrets
echo "your-db-password" | docker secret create db_password -
echo "your-jwt-secret" | docker secret create master_secret -
echo "your-auth-client-secret" | docker secret create auth_client_secret -
Reference in docker-compose.yml:
version: '3.8'
services:
eudiplo:
image: eudiplo/backend:latest
secrets:
- db_password
- master_secret
- auth_client_secret
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
MASTER_SECRET_FILE: /run/secrets/master_secret
AUTH_CLIENT_SECRET_FILE: /run/secrets/auth_client_secret
secrets:
db_password:
external: true
master_secret:
external: true
auth_client_secret:
external: true
File-based Secrets
Docker Secrets are mounted as files at /run/secrets/<secret_name>. The application reads secret values from these files at startup.
Option 2: Vault Agent Sidecar¶
Run HashiCorp Vault Agent alongside the application to inject secrets:
services:
vault-agent:
image: hashicorp/vault:latest
command: agent -config=/vault/config/agent.hcl
volumes:
- vault-secrets:/vault/secrets
- ./vault-agent-config.hcl:/vault/config/agent.hcl:ro
eudiplo:
image: eudiplo/backend:latest
depends_on:
- vault-agent
volumes:
- vault-secrets:/vault/secrets:ro
environment:
DB_PASSWORD_FILE: /vault/secrets/db_password
MASTER_SECRET_FILE: /vault/secrets/master_secret
volumes:
vault-secrets:
Vault Agent config (vault-agent-config.hcl):
vault {
address = "https://vault.example.com:8200"
}
auto_auth {
method "approle" {
config = {
role_id_file_path = "/vault/config/role-id"
secret_id_file_path = "/vault/config/secret-id"
}
}
}
template {
source = "/vault/config/secrets.ctmpl"
destination = "/vault/secrets/env"
}
Option 3: Environment File Encryption¶
For simpler setups, encrypt the .env file at rest:
# Encrypt .env with age or sops
age -e -r age1... .env > .env.encrypted
# Decrypt at deployment time
age -d .env.encrypted > .env
docker compose up -d
rm .env # Remove plaintext immediately
Why Encryption Key Is Different¶
The ENCRYPTION_KEY uses application-level fetching (via ENCRYPTION_KEY_SOURCE) because:
- TypeORM Transformer Singleton: The encryption transformer must be initialized before any database operations
- Runtime-Only Access: The key is fetched once at startup and kept only in memory
- Built-in Support: Configure
ENCRYPTION_KEY_SOURCE=vaultin the Full deployment to fetch the key from Vault at runtime
For all other secrets, infrastructure-level injection (Docker Secrets, Vault Agent) is preferred.
High Availability¶
Docker Compose is not suitable for high-availability production deployments because:
- ❌ Single host only (no multi-node support)
- ❌ No automatic failover
- ❌ Limited scaling capabilities
- ❌ Manual orchestration required
For production with HA requirements, use:
- ✅ Kubernetes (see Kubernetes Deployment)
- ✅ Docker Swarm (basic orchestration)
- ✅ Managed container services (AWS ECS, Azure Container Instances)
Vault Production Setup¶
Vault Dev Mode
The full deployment uses Vault in dev mode, which is NOT production-safe. Data is ephemeral and unsealing is automatic.
For production Vault:
- Use production mode with persistent storage
- Configure auto-unsealing (AWS KMS, Azure Key Vault, GCP KMS)
- Enable audit logging
- Set up backup and disaster recovery
- Use access control policies (ACLs)
Resources:
Database Considerations¶
For production PostgreSQL:
- ✅ Enable connection pooling (PgBouncer)
- ✅ Configure regular backups (pg_dump, WAL archiving)
- ✅ Set up replication for high availability
- ✅ Monitor performance metrics
- ✅ Use volume backups or external storage
Logging & Monitoring¶
Configure centralized logging:
Or use external logging (Loki, ELK):
When to Use Docker Compose vs Kubernetes¶
Use Docker Compose When¶
✅ Local development and testing
✅ Single-server deployment with moderate traffic
✅ Quick demos and proof of concepts
✅ Internal tools with low availability requirements
✅ Cost-effective small-scale deployments
Use Kubernetes When¶
✅ Production environments with high availability needs
✅ Multi-node clusters for scalability
✅ Auto-scaling based on load
✅ Zero-downtime deployments
✅ Cloud-native infrastructure
✅ Complex networking and service mesh
See: Kubernetes Deployment Guide
Advanced Configuration¶
Using External Database¶
Instead of containerized PostgreSQL, use external database:
Edit docker-compose.yml:
services:
eudiplo:
environment:
- DB_HOST=your-database-host.com
- DB_PORT=5432
- DB_SSL=true # Enable SSL for external DB
# Remove database service and dependency
Remove database service from docker-compose.yml.
Custom Docker Network¶
Connect to existing Docker network:
Build from Source¶
Instead of using pre-built images:
services:
eudiplo:
build:
context: ../..
dockerfile: Dockerfile
target: eudiplo
# Remove 'image' line
Build and start:
Cleanup¶
Remove All Containers¶
Remove Volumes (Data Loss)¶
Remove Images¶
Complete Cleanup¶
# Remove everything including volumes
docker compose down -v --rmi all --remove-orphans
# Remove dangling images
docker image prune -a
# Remove unused volumes
docker volume prune
Support & Resources¶
- Documentation: https://openwallet-foundation-labs.github.io/eudiplo/latest/
- GitHub Issues: https://github.com/openwallet-foundation-labs/eudiplo/issues
- Discord Community: https://discord.gg/58ys8XfXDu
- Docker Compose Docs: https://docs.docker.com/compose/
Next Steps¶
- 📖 Explore Architecture to understand EUDIPLO design
- 🔐 Configure Key Management
- 🔑 Set up API Authentication
- ☸️ Deploy to Kubernetes for production
- 📊 Enable Monitoring