Skip to main content

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:

  1. Container logs: docker logs <container-id>
  2. Health check status: docker ps (see STATUS column)
  3. 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