Services

A service is a long-running container, or a one-shot job. You declare services under the top-level services key in up.yaml. up apply converges them: it builds or pulls the image, deploys it, waits for the health check, then shifts traffic. Unknown keys on a service are rejected, so a typo fails at apply time.

The smallest working service:

services:
  - name: api
    source:
      type: image
      image: nginx:latest
    port: 8080

Identity

name

The service’s unique name within the environment. It is the handle you use to deploy, log, and reference the service.

name: api

type

A long-running container by default, or a one-shot job. Jobs run to exit and are covered in detail below.

type: job

Source

The source block says where the image comes from. It is required.

[!NOTE] command and args configure the process inside the image. They are covered under Command and arguments.

Image source

Deploy an image you already have:

source:
  type: image
  image: nginx:latest

image

The image reference to deploy, for example nginx:latest or ghcr.io/myorg/api:1.2.3. Required.

registry

The name of a registry from the registries section, used when the image needs private credentials. References are by name, not ID.

source:
  type: image
  image: ghcr.io/myorg/api:1.2.3
  registry: ghcr

Git source

Build from a repository. The image is built on a build node and pushed to the output registry before it is deployed:

source:
  type: git
  url: git@github.com:org/repo.git

url

The repository to clone. Required.

ref

The ref to build: a branch name, a tag, or a full commit SHA. The default, an empty ref, means the repository default branch. Use a commit SHA when a deploy must land on the exact revision it was tested against, e.g. when a CI system pins deployments after a build.

source:
  type: git
  url: git@github.com:org/repo.git
  ref: v1.2.3

token

token

The secret used to connect to private repositories such as ghcr.io or a self-hosted Git host like GitLab or Gitea. It is never written to logs or error messages.

source:
  type: git
  url: git@github.com:org/repo.git
  token: $GIT_TOKEN

Building from source

A git source needs a build block that selects the backend. Both backends share the options documented under Common build options.

dockerfile backend

Build from a Dockerfile. A minimal config builds Dockerfile from the repository root:

build:
  type: dockerfile

dockerfile

The path to the Dockerfile. Defaults to Dockerfile.

build:
  type: dockerfile
  dockerfile: Dockerfile.prod

target

The multi-stage build target to stop at.

build:
  type: dockerfile
  target: production

railpack backend

Railpack auto-detects your language and framework and builds without a Dockerfile. See the Railpack documentation for what it supports and how it configures each language.

build:
  type: railpack

Common build options

node

The name of the node that runs the build. It must carry the build role.

build:
  type: dockerfile
  node: builder-1

root

The subdirectory within the cloned repository that the builder treats as the app root. It is useful for monorepos, where the source for a service lives in a subdirectory rather than at the root of the repository. The value is a relative path, so it must not start with / and must not contain .. segments. Empty means the repository root, which is the default.

For the dockerfile backend, the context given to the build is the root directory, so dockerfile and every COPY inside the Dockerfile are relative to the root. For the railpack backend, the root directory is scanned for the app, so the start command and language configuration of that directory are used.

build:
  type: railpack
  root: apps/api

output_registry

The name of the registry that receives the built image. Defaults to the default registry, so this is only needed when the build output should go somewhere else.

build:
  type: dockerfile
  output_registry: ghcr

output_prefix

By default the built image is pushed under the name host/project_name/service_name. Set output_prefix to override the middle segment. This matters on ghcr.io, where the first part of an image name must be your GitHub username or organisation name: with output_prefix: myorg the image becomes host/myorg/project_name/service_name. Defaults to the project name.

build:
  type: dockerfile
  output_prefix: myorg

cache

Builds reuse layers from previous builds through a cache image stored in the output registry, so repeat builds only redo the steps that changed. Set cache to false to force a fully cold build, for example when you suspect a stale layer is hiding a problem. Defaults to true.

build:
  type: railpack
  cache: false

Build environment

The service env is also available during the build. For a railpack build, the variables enter the Railpack build environment, so RAILPACK_* variables configure the build. See the Railpack documentation for the full list. See Environment variables for how env works.

Port

The container port your process listens on. Optional: services that read the PORT environment variable, and jobs, can omit it.

port: 8080

Command and arguments

The command and args fields configure what the container runs: command replaces the image entrypoint and args supplies the arguments after it. By default the container runs the image’s own entrypoint and command untouched. Each field accepts a list of strings, or one plain string that is split on whitespace. Quote characters are not interpreted, so an argument that must contain a real space needs the list form.

The common case is an image whose process is configured through arguments only. NATS, for example, has no environment toggle for JetStream; with args the official image runs as-is and no wrapper Dockerfile is needed:

services:
  - name: nats
    source:
      type: image
      image: nats:2-alpine
    args: -js -sd /data
    volumes:
      - source: nats-data
        target: /data

command

The container entrypoint override, applied instead of the image ENTRYPOINT. Omit it to keep the image entrypoint. Use it when the image entrypoint wraps the process you actually want, or when the image has none:

command: ["nats-server"]

args

The arguments passed to the entrypoint, replacing the image CMD. Omit it to keep the image command. Setting args alone keeps the image entrypoint, which is how argv-configured images like NATS or Memcached are meant to run:

args:
  - -js
  - -sd
  - /data

A change to either field redeploys the service, and up diff reports it as a command or args change.

Domains

Expose the service on the public internet. Each entry is a hostname the proxy routes to this service on ports 80 and 443. For a domain to resolve publicly, point its CNAME (or A record) at the IP address of the node that runs the deployment. For local-only access, add a domain like api.localhost, which resolves on the current device with no DNS setup.

domains:
  - api.myapp.com

Domains also drive the injected UP_PUBLIC_DOMAIN and UP_PUBLIC_URL variables, documented under Environment variables.

Wildcard domains

A domain can be a single-label wildcard, for example *.up.karabo.io, so one service serves every tenant subdomain without listing each one:

domains:
  - "*.up.karabo.io"

The proxy matches the wildcard against the request Host and forwards that header unchanged, so the application reads the tenant from it. The wildcard matches exactly one label, so tenant.up.karabo.io matches *.up.karabo.io but tenant.app.up.karabo.io does not. Point the DNS wildcard record at the node that runs the deployment, the same way you point a plain domain.

Wildcard domains are HTTP-only. You cannot enable proxy.ssl for a wildcard, because a certificate authority issues a wildcard certificate only after a DNS challenge and up does not perform one. The apply fails with a validation error if you try. Terminate TLS in front of the node if the service must be served over HTTPS.

UP_PUBLIC_DOMAIN and UP_PUBLIC_URL skip wildcard domains, because a wildcard is not a usable URL. If the service needs those variables, declare a concrete domain in addition to the wildcard.

Proxy

The proxy block controls TLS for the service’s domains. It has two options.

ssl

Defaults to false. Set it to true to serve HTTPS with certificates managed by the proxy (Caddy).

proxy:
  ssl: true

ssl_redirect

When ssl is on, HTTP requests are redirected to HTTPS. Enabled by default; set it to false to pass HTTP through untouched.

proxy:
  ssl: true
  ssl_redirect: false

Health checks

A health check gates the rollout: the new container is promoted only once the probe passes, and a failing probe fails the deployment. Probes run inside the container network, so they resolve the service by DNS alias without publishing ports.

A minimal HTTP health check:

health:
  path: /health

Probe types

The probe kind is set with type: http (default), tcp, or command.

http

Probe an HTTP endpoint. path is required.

health:
  type: http
  path: /health
  port: 8080

port defaults to the service port, then the PORT environment variable.

tcp

Probe a TCP port. port is required.

health:
  type: tcp
  port: 5432

command

Run an argv list inside the container. command is required.

health:
  type: command
  command:
    - pg_isready
    - -U
    - postgres

Timing

The timing options apply to every probe type.

interval

Seconds between attempts. Defaults to 3s.

health:
  path: /health
  interval: 10s

timeout

Seconds per attempt. Defaults to 5s.

health:
  path: /health
  timeout: 5s

retries

The allowed failures before the check fails. When omitted, up retries until the deployment deadline.

health:
  path: /health
  retries: 3

start_period

Seconds to wait before probing begins. Lets a slow process boot first.

health:
  path: /health
  start_period: 5s

Restart

Control when the container restarts. A minimal config:

restart: always

policy

The restart strategy: no, always, on-failure, or unless-stopped. Defaults to on-failure, which restarts the container when it exits non-zero and stops retrying after the cap, so a crash loop does not run forever. Choose unless-stopped when the container should restart on any exit, or always when it should restart even after a deliberate stop.

The runtime does not restart on-failure containers when the daemon or the machine restarts. To cover that, the control node redeploys any service it finds with a stopped container when it starts, and up apply does the same for every declared service that is not running.

retries

The restart attempt cap, used with on-failure. Defaults to 5. Raise it for a service that recovers after several crashes, or use policy: always to restart without a cap.

restart:
  policy: on-failure
  retries: 3

Environment

The env map holds the variables passed to the container, with ${{ }} references to other services, $VAR host expansion, and injected UP_* variables. See Environment variables for the complete reference.

env:
  NODE_ENV: production

Volumes

Persist data with volumes. A minimal config:

volumes:
  - source: data
    target: /var/lib/postgresql

source

The name of a managed named volume. Host paths are rejected; only names matching [a-zA-Z0-9][a-zA-Z0-9_.-]* are allowed.

target

The absolute container path to mount the volume at. The container root is not allowed.

Scoping and persistence

Volumes are scoped per environment. The logical name you write in up.yaml is what up reports, while the physical Docker volume is prefixed with the environment id, for example up-{environmentId}-data. Two environments of the same project never share data, and the volume survives redeploys and service removal. See Environments for details.

Lifecycle scripts

pre_deploy and post_deploy run a shell command in a side container that shares the service image, environment, volumes, and network. The deployment waits for the command to exit, and a non-zero exit fails the deployment. Chain commands with && to run several steps.

pre_deploy

Runs before the service starts. Use it for migrations or setup that must complete first. A failure aborts the deployment before the service is touched.

pre_deploy: "rake db:migrate && rake assets:precompile"

post_deploy

Runs after the new container is promoted. Use it for seed data or cache warmers. The previous deployment is kept running until the new one succeeds; if a post_deploy hook fails, traffic rolls back to the previous deployment.

post_deploy: "./bin/seed"

Jobs

A type: job service is a one-shot task, useful for migrations, backfills, and periodic work you trigger on demand. Its container runs to completion and the deployment succeeds only on a zero exit code. Jobs are never promoted, never made active, and never restarted as long-running services:

services:
  - name: db-migrate
    type: job
    source:
      type: image
      image: myapp:1.2.3

Job logs stream through the same up service deploy and up apply flow. Jobs cannot be referenced by other services (see Environment variables), but a job may read values from a service.