Skip to main content
A service is one container. Give it a public port and it gets its own URL; services in the same deployment reach each other by name over a private network. Service names match ^[a-z][a-z0-9-]{0,30}$ and must be unique within the spec.

build or image

Each service either builds from your repo or pulls a prebuilt image — exactly one.

build

With no target, slipway builds the last stage in your Dockerfile, exactly like docker build with no --target. That’s a common source of surprise: if your Dockerfile ends with a development or dev-envs stage, that’s the image you’ll deploy, and it’s the one that has to build. Name the stage you actually want to run.
Builds are content-addressed: the image tag comes from the commit SHA plus a hash of the build inputs. If a previous deploy on the same commit produced the same inputs, the build is skipped and the cached image is reused.
Passing a secret into build.args is safe by itself, but if your Dockerfile does RUN echo $TOKEN the value gets baked into the image layer. Keep credentials out of build args unless your Dockerfile discards them inside the same RUN step.

image

For private registries, add the credentials once under Settings → Registries — see Private registries. Slipway wires them in automatically; no per-service field needed.

ports

Ports the container listens on. Omit ports and the service is a worker — internal-only, no URL, no readiness gate at cutover.
One public port per service. For path-based routing across services, define each separately and put a reverse proxy in your repo.

env

Environment variables, as a plain map:
See Secrets & variables for the ${...} substitution syntax.

Variables slipway sets for you

Every service with a public port also gets these, set by the platform: You can’t hardcode that hostname: it carries the environment-instance id, and every pull-request preview gets a different one. Read it from the environment instead. If you set either name yourself in env, yours wins.
If your framework checks the Host header, it needs this value. Django (ALLOWED_HOSTS), Rails (config.hosts), webpack-dev-server and Vite (allowedHosts) all reject requests for a hostname they weren’t told about, and answer the public URL with a 400 or 403 while still looking perfectly healthy on their port. Feed them SLIPWAY_PUBLIC_HOST:
slipway checks for this at cutover and writes a warning into the deploy log naming the service and status if it happens, so you don’t have to guess why a green deploy serves a 403.

files

Project committed repo files into the container, read-only — the cluster-native counterpart of a Docker Compose bind mount, for config and seed files: a Postgres init.sql, an nginx.conf, a fixtures file. Each entry is <repo-path>:<container-path> — the source is a path in your repo relative to the spec file’s directory, the target an absolute path in the container.
slipway reads each file from your repo at the deploy commit and mounts it with subPath, so only that one file lands at the target — the rest of the directory (the image’s own contents) is untouched. A Compose ./init.sql:/docker-entrypoint-initdb.d/init.sql bind mount is translated to this automatically.
files are projected through a plaintext ConfigMap, so they’re for non-secret config and seed data only. Keep credentials in secrets (${secret.X}) and reference them from env — never from a committed, mounted file.
Constraints: small text files (each under ~1 MB), and the source must stay inside the repo (no absolute or .. paths). For data that must persist, or large/binary content, use a named volume instead.

depends_on

Sibling services that must be ready before this one starts — Compose’s service_healthy semantics. All services are created at once so images pull and volumes attach in parallel, but a service’s own process doesn’t start until every dependency passes its readiness probe. It waits instead of crash-looping. If a dependency never comes up within the deploy window, the deployment fails with the dependency named in the reason, rather than going live with a service that never started.
Dependencies must be services in the same spec. A service can’t depend on itself or form a cycle — both are rejected at parse time. A long dependency chain still adds up (each link waits for the one before it), and depends_on guarantees start ordering, not zero-downtime ordering (services run a single replica).

healthcheck

Three probe phases — set any subset:
  • startup — runs first and blocks the others until it passes. For slow-booting apps.
  • readiness — gates traffic and gates cutover: a deploy isn’t healthy and its URL doesn’t go live until every public service is ready.
  • liveness — runs continuously; failures restart the container.
If a ported service has no healthcheck, slipway adds a default TCP readiness probe on its public port, so a URL only goes live once the app accepts connections. Declaring any healthcheck replaces that default.

Handlers

Each probe takes exactly one handler:

Timing

All handlers accept the standard knobs — initialDelaySeconds, periodSeconds, timeoutSeconds, failureThreshold, and successThreshold (readiness/liveness only). Omit them to use sensible defaults. For most web apps a single readiness probe is enough. Add startup only when boot takes more than ~30 seconds; add liveness only when you’ve seen the app deadlock in a way readiness doesn’t catch. A worker with no ports needs no probe — it’s ready as soon as it’s running.

Resource limits

You don’t size containers in the spec. CPU, memory, and disk caps come from your plan and are applied automatically. If a deploy asks for more than your plan allows, it fails with a clear error.