Multi-Container Template¶
This template is for deploying multiple services together (app + database, app + cache, etc.).
Use Cases¶
- Web app with PostgreSQL database
- API with Redis cache
- Full-stack application with multiple services
- Microservices architecture
Template¶
version: '3.8'
services:
# Your application
app:
build: .
container_name: my-app
restart: unless-stopped
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
labels:
# Enable Traefik for this container
- traefik.enable=true
# Router configuration
- traefik.http.routers.my-app.rule=Host(`app.egygeeks.com`)
- traefik.http.routers.my-app.entrypoints=websecure
- traefik.http.routers.my-app.tls.certresolver=letsencrypt
# Service configuration (port your app listens on)
- traefik.http.services.my-app.loadbalancer.server.port=3000
networks:
- traefik_public
- internal
# PostgreSQL Database
db:
image: postgres:15-alpine
container_name: my-app-db
restart: unless-stopped
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- internal
# Redis Cache (optional)
cache:
image: redis:7-alpine
container_name: my-app-cache
restart: unless-stopped
networks:
- internal
volumes:
postgres-data:
networks:
traefik_public:
external: true
internal:
driver: bridge
Customization Steps¶
1. Replace App Name¶
Find and replace my-app throughout:
- Container names:
my-app,my-app-db,my-app-cache - Router names:
my-app - Service names:
my-app
2. Set Your Domain¶
3. Configure Database¶
Change database credentials:
Never use default passwords in production!
Replace password with a strong password. Store in .env file on server.
Update connection string:
4. Remove Optional Services¶
Don't need Redis? Remove the cache service:
And remove from app dependencies:
Network Architecture¶
This template uses two networks for security:
traefik_public (External)¶
- Only the
appservice connects here - Allows Traefik to route traffic to your app
- External network shared across all apps
internal (Private)¶
- All services connect here
- App communicates with database/cache
- Never exposed to the internet
- Database and cache are NOT accessible externally
Security Best Practice
Only your application service should be on traefik_public. Never expose databases or caches directly to Traefik.
Database Options¶
PostgreSQL (Default)¶
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres-data:/var/lib/postgresql/data
MySQL¶
db:
image: mysql:8-debian
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MYSQL_ROOT_PASSWORD=rootpassword
volumes:
- mysql-data:/var/lib/mysql
MongoDB¶
db:
image: mongo:7
environment:
- MONGO_INITDB_DATABASE=mydb
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- mongo-data:/data/db
Environment Variables¶
Using .env File (Recommended)¶
Create .env file on server:
DATABASE_URL=postgresql://postgres:strong-password@db:5432/mydb
REDIS_URL=redis://cache:6379
API_KEY=your-secret-key
Reference in docker-compose.yml:
Never commit .env to git
Add .env to .gitignore. Store secrets only on the server.
Complete Example: Next.js + PostgreSQL¶
version: '3.8'
services:
app:
build: .
container_name: nextjs-app
restart: unless-stopped
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/appdb
depends_on:
- db
labels:
- traefik.enable=true
- traefik.http.routers.nextjs-app.rule=Host(`myapp.egygeeks.com`)
- traefik.http.routers.nextjs-app.entrypoints=websecure
- traefik.http.routers.nextjs-app.tls.certresolver=letsencrypt
- traefik.http.services.nextjs-app.loadbalancer.server.port=3000
networks:
- traefik_public
- internal
db:
image: postgres:15-alpine
container_name: nextjs-app-db
restart: unless-stopped
environment:
- POSTGRES_DB=appdb
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- internal
volumes:
postgres-data:
networks:
traefik_public:
external: true
internal:
driver: bridge
With .env file:
Deployment¶
After creating your configuration:
- Create
.envfile on server with secrets - Copy the GitHub Actions workflow template
- Push to GitHub
- Deployment runs automatically
Or deploy manually on the server:
Troubleshooting¶
App can't connect to database¶
Check container names match connection string:
# Connection string uses 'db' as hostname
DATABASE_URL=postgresql://postgres:password@db:5432/mydb
# Service must be named 'db'
db:
image: postgres:15-alpine
Verify both are on same network:
Database data lost after restart¶
Ensure volume is configured:
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data: # Must be declared at bottom
Port conflicts¶
Each service needs unique ports internally, but don't expose database ports:
# ❌ Don't do this
db:
ports:
- "5432:5432" # Never expose database ports!
# ✅ Do this instead
db:
# No ports section - only accessible via internal network