Source: https://crabglamp.com/docs/app-vms/how-to/deploy-a-compose-stack
Last updated: 2026-06-18
Type: how-to

Compose stacks let you run multiple containers together on a single App VM. One service is exposed over the public URL; the rest are internal, reachable from each other by service name.

## Before you start

You need a running App VM with compose enabled (choose **Compose stack** when creating), or create a new one. All services must reference pre-built images from a registry unless you supply a public git URL (which allows `build:` services).

## 1. Choose a source

When you create a compose App VM, pick one of two sources:

**Paste a compose file** — enter the contents of your `docker-compose.yml` directly. Every service must have an `image:` field pointing at a pre-built, publicly reachable or credentialed image. The `build:` directive is not supported with a pasted file.

**Public git URL** — enter an `https://` URL to a public repository. The platform clones the repo and builds services with `build:` directives on the VM. Optionally specify a branch, tag, or commit SHA to pin; a subdirectory path if your compose file is not at the repo root; and an alternate compose file path within that directory.

## 2. Designate the ingress service

One service receives public traffic. Select it in the **Ingress service** dropdown. When that service declares a single port in your compose file, the **Ingress port** field is pre-filled with it; otherwise enter the port the service listens on (1–65535). The declared ports are shown beneath the field as a hint.

You can change the ingress service or port after creation: open the App's detail page, edit the fields in the **Configuration** card, and choose **Save & redeploy**. If a deploy failed because the ingress port was wrong, the error includes a link straight to those fields.

All other services are reachable internally by their service name. For example, if your compose file has a `db` service, your web service connects to it as `db:5432`.

## 3. Set environment variables

Supply environment variable values on the `vars` tab or via `PUT /api/app-vms/[id]/env`. In your compose file, reference them with `${VAR_NAME}`. The platform substitutes values at deploy time; services receive the resolved values. Values are treated literally — a `$` in a value (for example a generated password) is preserved, not re-interpreted.

## 4. Use named volumes

Declare named volumes in your compose file. They persist for the life of the App VM and are deleted when you destroy it. Do not bind-mount host paths — the platform rejects them. See [Unsupported features](#unsupported-features) below.

## 5. Registry credentials

A single account-level registry credential applies to all images in the stack. Select it from the **Registry credential** dropdown when creating or editing the App. Using images from two different private registries in the same stack is not supported yet — keep all private images on a single registry; public images can be mixed in freely.

## 6. Deploy

Choose **Deploy** to start the stack. The App moves to `deploying`. When all services are healthy, traffic routes to the ingress service and the App returns to `running`.

To redeploy without changing the source — for example to pull updated images — use **Redeploy** on the App's detail page (for pasted stacks) or deploy again with a new git ref (for git-backed stacks).

## Unsupported features

The following configuration in your compose file causes the App creation or deploy to be rejected. The error message names the field. For any of these, a [Plain VM](/docs/plain-vms/tutorial/get-started) gives you full host control.

| Field | Reason |
|---|---|
| `privileged: true` | requires host root — not supported on App VMs |
| `cap_add` | added Linux capabilities — not supported on App VMs |
| `devices` | host device access — not supported on App VMs |
| `network_mode: host` or `network_mode: container:…` | host or shared network namespace — not supported on App VMs |
| `pid: host`, `ipc: host`, `uts: host`, `userns_mode: host` | host namespace sharing — not supported on App VMs |
| `security_opt: …:unconfined` | disabling security profiles — not supported on App VMs |
| Absolute host bind mounts in `volumes` | mounts a host path into the container — use a named volume instead |
| `build:` with a pasted compose file | builds require a git source |
| `extends.file` with an absolute path | reads a host file — not supported on App VMs |
| Top-level `include:` | not supported — combine services into one compose file |
| Top-level `secrets` or `configs` with an absolute `file:` | reads a host file — not supported on App VMs |
| Top-level `networks` using `macvlan` or `ipvlan` driver | host L2 network access — not supported on App VMs |

The platform also ignores `ports:` declarations — only the designated ingress service is published. `container_name`, `deploy.replicas > 1`, and `profiles` are silently ignored (warnings in the deploy log).

## Services cap

A compose stack may contain at most 10 services. Stacks over this limit are rejected at creation time.
