The services section defines the containers that make up your application. Each service runs one or more containers across your cluster.
Service definition
A basic service requires at minimum an image:
services:
web:
image: nginx:alpine
Image and build
Using an image
Specify a container image from a registry:
services:
web:
image: nginx:1.25-alpine
pull_policy: always # always, missing, or never
Pull policies:
always - Always pull the image from the registry
missing - Pull only if not available locally (default)
never - Never pull, use only local images
Building images
Build images from source during deployment:
services:
api:
build:
context: ./api
dockerfile: Dockerfile
args:
- VERSION=1.0
References: pkg/client/compose/service.go:18-145
Command and entrypoint
Override the image’s default command and entrypoint:
services:
worker:
image: python:3.11
entrypoint: ["/usr/local/bin/python"]
command: ["worker.py", "--mode", "production"]
Environment variables
Set environment variables for containers:
services:
app:
image: myapp
environment:
- NODE_ENV=production
- API_KEY=secret
- DEBUG=false
env_file:
- .env
- .env.production
Variables in env_file are read from the local filesystem where you run uc deploy.
References: pkg/api/service.go:249-250, pkg/client/compose/service.go:36-44
Ports and networking
HTTP/HTTPS ports with x-ports
Publish HTTP/HTTPS services via Uncloud’s Caddy reverse proxy:
services:
web:
image: nginx
x-ports:
# HTTPS with automatic TLS
- example.com:80/https
# HTTP without TLS
- 8080:80/http
# Just container port (uses cluster domain)
- 80/https
Format: [hostname:][published_port:]container_port/protocol
When you specify a hostname, Uncloud automatically obtains a TLS certificate via Let’s Encrypt.
TCP/UDP ports with host mode
Bind TCP/UDP ports directly to host machines:
services:
database:
image: postgres:16
x-ports:
- 5432:5432/tcp@host
- 0.0.0.0:5432:5432/tcp@host # Bind to specific IP
Format: [host_ip:]host_port:container_port/protocol@host
Custom Caddy configuration
For advanced routing, use x-caddy instead of x-ports:
services:
app:
image: myapp
x-caddy: |
app.example.com {
reverse_proxy {{ upstreams 8080 }}
# Custom path routing
handle /api/* {
reverse_proxy {{ upstreams 8081 }}
}
}
x-ports and x-caddy are mutually exclusive for ingress ports. You can use host mode ports (@host) with either.
References: pkg/api/port.go:20-82, pkg/api/service.go:55-70
Volumes
Mount volumes into containers:
services:
app:
image: myapp
volumes:
# Named volume
- app-data:/data
# Bind mount (read-only)
- ./config.json:/etc/app/config.json:ro
# Long syntax with options
- type: volume
source: app-data
target: /data
volume:
nocopy: true
# Tmpfs (in-memory)
- type: tmpfs
target: /tmp
tmpfs:
size: 10485760 # 10MB
volumes:
app-data:
See Volumes for detailed volume configuration.
References: pkg/client/compose/service.go:257-293
Configs
Mount configuration files into containers:
services:
web:
image: nginx
configs:
- source: nginx-config
target: /etc/nginx/nginx.conf
mode: 0644
configs:
nginx-config:
file: ./nginx.conf
See Configs for details.
Health checks
Define container health checks:
services:
api:
image: myapi
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
start_interval: 5s
Health check options:
test - Command to run (formats: ["CMD", args...] or ["CMD-SHELL", "command"])
interval - Time between checks (default: 30s)
timeout - How long to wait for check
retries - Consecutive failures before unhealthy
start_period - Grace period before retries count
start_interval - Interval during start period
References: pkg/api/service.go:414-432, pkg/client/compose/service.go:147-173
Capabilities
Add or drop Linux capabilities:
services:
net-admin:
image: myapp
cap_add:
- NET_ADMIN
- SYS_TIME
cap_drop:
- ALL
References: pkg/api/service.go:241-243
User
Run containers as a specific user:
services:
app:
image: myapp
user: "1000:1000" # user:group or UID:GID
References: pkg/api/service.go:269
Privileged mode
Run containers with extended privileges:
services:
docker:
image: docker:dind
privileged: true
Privileged mode is a security risk. Only use when absolutely necessary.
References: pkg/api/service.go:260
Init process
Run an init process inside the container:
services:
app:
image: myapp
init: true
References: pkg/api/service.go:256
Logging
Configure container logging:
services:
app:
image: myapp
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Default driver is local if not specified.
References: pkg/api/service.go:257-258
Sysctls
Set namespaced kernel parameters:
services:
app:
image: myapp
sysctls:
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
References: pkg/api/service.go:266-267
Devices
Mount host devices into containers:
services:
usb-app:
image: myapp
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0:rw"
- "/dev/sda:/dev/xvda"
References: pkg/client/compose/service.go:186-196
GPUs
Access GPU devices:
services:
ml-worker:
image: tensorflow/tensorflow:latest-gpu
gpus: all
Or for specific GPUs:
services:
ml-worker:
image: ml-app
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
References: pkg/client/compose/service.go:204-226
Stop grace period
Configure how long to wait after SIGTERM before SIGKILL:
services:
app:
image: myapp
stop_grace_period: 30s
Default is 10 seconds.
References: pkg/api/service.go:72-74
Deploy section
Configure deployment behavior:
services:
web:
image: nginx
deploy:
mode: replicated
replicas: 3
update_config:
order: start-first
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
See Deploy for complete deployment configuration.
Complete example
services:
web:
image: nginx:alpine
x-ports:
- example.com:80/https
volumes:
- web-content:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
environment:
- BACKEND_URL=http://api:8080
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost"]
interval: 30s
timeout: 5s
retries: 3
deploy:
replicas: 2
update_config:
order: start-first
resources:
limits:
cpus: '0.25'
memory: 128M
api:
build: ./api
environment:
- DATABASE_URL=postgres://db:5432/myapp
volumes:
- api-logs:/var/log/api
user: "1000:1000"
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
memory: 512M
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=secret
stop_grace_period: 60s
deploy:
replicas: 1
volumes:
web-content:
api-logs:
db-data: