Deploy Multi-service Applications
Deploy applications with multiple interconnected services like web frontends, API backends, databases, and worker processes.
What You'll Learn
- Deploy multiple services from one
compose.yaml - Connect services using internal DNS
- Deploy specific services independently
- Scale services across machines
Basic Multi-service App
Here's a typical three-tier application:
my-app/
├── compose.yaml
├── frontend/
│ ├── Dockerfile
│ └── src/
├── api/
│ ├── Dockerfile
│ └── src/
└── README.md
compose.yaml
services:
frontend:
build: ./frontend
image: my-app-frontend:latest
x-ports:
- app.example.com:3000/https
environment:
API_URL: http://api:8000
depends_on:
- api
deploy:
replicas: 2
api:
build: ./api
image: my-app-api:latest
x-ports:
- api.example.com:8000/https
environment:
DATABASE_URL: postgres://db:5432/myapp
depends_on:
- db
deploy:
replicas: 3
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
db-data:
Deploy All Services
uc deploy
Output:
Building service frontend...
[+] Building 8.4s (12/12) FINISHED
Building service api...
[+] Building 15.2s (14/14) FINISHED
Pushing images to cluster...
✓ Pushed my-app-frontend:latest to machine-1, machine-2
✓ Pushed my-app-api:latest to machine-1, machine-2, machine-3
Deployment plan:
┌───────────┬──────────┬────────┬─────────┬────────┐
│ Machine │ Service │ Create │ Update │ Remove │
├───────────┼──────────┼────────┼─────────┼────────┤
│ machine-1 │ frontend │ 1 │ 0 │ 0 │
│ machine-1 │ api │ 1 │ 0 │ 0 │
│ machine-1 │ db │ 1 │ 0 │ 0 │
│ machine-2 │ frontend │ 1 │ 0 │ 0 │
│ machine-2 │ api │ 1 │ 0 │ 0 │
│ machine-3 │ api │ 1 │ 0 │ 0 │
└───────────┴──────────┴────────┴─────────┴────────┘
Proceed? (yes/no): yes
Deploying...
✓ Created db-1 on machine-1
✓ Created api-1 on machine-1
✓ Created api-2 on machine-2
✓ Created api-3 on machine-3
✓ Created frontend-1 on machine-1
✓ Created frontend-2 on machine-2
Deployment successful!
Service Communication
Services communicate using Uncloud's built-in DNS resolution.
Internal DNS
Each service is accessible by its service name:
services:
frontend:
environment:
API_URL: http://api:8000 # Resolves to API service containers
api:
environment:
DATABASE_URL: postgres://db:5432/myapp # Resolves to db container
Uncloud's internal DNS automatically resolves service names to container IP addresses. Learn more in Internal DNS.
Load Balancing
When a service has multiple replicas, DNS returns all container IPs. Your client handles load balancing:
services:
api:
deploy:
replicas: 3 # DNS returns 3 IPs for 'api'
For HTTP services, use Caddy's built-in load balancing by exposing the service with x-ports.
Deploying Specific Services
Deploy One Service
Deploy only the frontend service:
uc deploy frontend
This deploys frontend without rebuilding or redeploying other services.
Deploy Multiple Services
Deploy frontend and API, skip database:
uc deploy frontend api
Deploy with Dependencies
Deploy API and all its dependencies (database):
uc deploy api --deps
Service Dependencies
Use depends_on to control startup order:
services:
api:
depends_on:
- db
- redis
Note: depends_on only affects deployment order, not runtime health checks. Ensure your application handles connection retries.
Advanced Patterns
Separate Build Contexts
Each service can have its own build context:
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
args:
NODE_ENV: production
api:
build:
context: ./api
dockerfile: docker/Dockerfile.prod
target: production
Machine Placement
Pin services to specific machines using x-machines:
services:
db:
image: postgres:16-alpine
x-machines:
- machine-1 # Database only on machine-1
volumes:
- db-data:/var/lib/postgresql/data
api:
build: ./api
deploy:
replicas: 3 # API distributed across all available machines
Mixed Build and Pre-built Images
Combine custom builds with public images:
services:
web:
build: . # Custom application
image: my-web:latest
redis:
image: redis:7-alpine # Public image, no build
nginx:
image: nginx:alpine # Public image, no build
Updating Multi-service Apps
Update All Services
# Make changes to frontend and API
vim frontend/src/app.js
vim api/src/handler.py
# Deploy all changes
uc deploy
Update One Service
# Only change frontend
vim frontend/src/app.js
# Deploy only frontend
uc deploy frontend
This rebuilds only the frontend image and updates only frontend containers.
Selective Rebuild
Build specific services without deploying:
# Build only API service
uc build api
# Deploy later (reuses built image)
uc deploy api --no-build
Example: Full-stack Application
Here's a complete example with web frontend, API backend, database, and background worker:
services:
web:
build: ./web
image: myapp-web:latest
x-ports:
- app.example.com:80/https
environment:
API_URL: http://api:8000
depends_on:
- api
deploy:
replicas: 2
api:
build: ./api
image: myapp-api:latest
x-ports:
- api.app.example.com:8000/https
environment:
DATABASE_URL: postgres://db:5432/myapp
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
deploy:
replicas: 3
worker:
build: ./api # Same codebase as API
image: myapp-api:latest
command: python -m celery worker
environment:
DATABASE_URL: postgres://db:5432/myapp
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
deploy:
replicas: 2
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
volumes:
db-data:
redis-data:
Deploy everything:
uc deploy
Troubleshooting
Service Can't Connect to Another Service
Check DNS resolution from inside a container:
# Get container ID
uc ps
# Test DNS resolution
docker exec <container-id> nslookup api
Ensure the service name matches exactly what's in compose.yaml.
Build Order Issues
If builds depend on each other, Uncloud builds in dependency order. To force a specific order, build manually:
uc build base-image
uc build app --no-cache
uc deploy --no-build
Deployment Hangs
If deployment seems stuck, check:
- Container logs:
docker logs <container-id> - Health check status:
docker ps(see STATUS column) - Port conflicts on machines
Service Updates Not Visible
Ensure you're rebuilding the correct service:
# Force rebuild without cache
uc build api --no-cache
# Deploy the rebuilt image
uc deploy api
Next Steps
- Updating Services - Learn different update strategies and rollback techniques
- Pre-built Images - Use images from Docker Hub and registries
- Build Options - Optimize builds with caching and build args