Skip to main content
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:

Build docs developers (and LLMs) love