$ aoox

DocsStacks & databases

Compose stacks

Deploy a docker-compose file from a repo as one multi-service stack, complete with a domain per service.

Source
A Git repo (compose file) or a template
Runs as
docker compose project aoox-<slug>
Status
idle → deploying → running | stopped | error

Application, compose stack, or template?

One repo, one process, one port? → Application
Several services talking to each other (web + worker + queue) with a docker-compose.yml? → Compose stack
Want a ready-made app (WordPress, n8n, …) without a repo? → Template

A compose stack trades some application features (health checks, blue/green, rollback, metrics) for the flexibility of running a compose file as-is — including any build:, networks, and volumes defined inside it.

Creating a stack

  1. Project page → New compose stack

    FieldNotes
    NameA unique slug; the compose project name = aoox-<slug> (its own group in Docker Desktop).
    Git repository / Branch / Git credentialSame as an application.
    Compose filePath relative to the repo root, defaulting to docker-compose.yml. Relative paths inside the file are resolved from that file's folder.
    Environment variablesSupports ${{vars.KEY}} (from the stack's linked variable set environments, in order; the stack's own env wins) and ${{database.<slug>.url}}.
  2. Click Deploy

    1. 01helpera one-off docker:29-cli container (compose + git)
    2. 02clonegit clone --depth 1 into a checkout volume
    3. 03env.aoox.env written from the resolved env
    4. 04updocker compose config → up -d --build --remove-orphans

    The helper's output streams into the Last action log panel (refreshing every 3 seconds while deploying). Git tokens and database passwords are redacted.

  3. Verify

    The stack page shows the containers compose created and their status. A stack status of running means up -d finished without error — it doesn't mean every service is healthy; check a service's logs with docker compose logs in the terminal.

Env & interpolation

Stack env ≠ container env
.aoox.env is used as the --env-file: its values become the source for ${VAR} interpolation in the compose file, they are not automatically injected into containers. Pass them through explicitly via environment: on the service.
Stack env in aoox
DATABASE_URL=${{database.app-db.url}}
APP_SECRET=secret
docker-compose.yml
services:
  web:
    build: .
    environment:
      DATABASE_URL: ${DATABASE_URL}   # from the stack env
      APP_SECRET: ${APP_SECRET}
  worker:
    build: .
    command: node worker.js
    environment:
      DATABASE_URL: ${DATABASE_URL}
  • An aoox managed database is reachable from the stack because the service joins the aoox network whenever there's a domain (see below); without a domain, add networks: [default, aoox] yourself plus an external network declaration.
Joining the aoox network manually
services:
  web:
    networks: [default, aoox]
networks:
  aoox:
    external: true

Access: domain or IP & port

The Access card on the stack page has two kinds of rows for each service you want reachable from outside:

TypeWhat you fill inResult
DomainService, container port, host, HTTPS toggleA Traefik label in the override file; requires a reverse proxy.
IP & portService, container port, host portA ports: line in the override file — the stack can then be opened at http://<server-ip>:<host port> without a domain. Each port has a Listens on choice: Public (default) or This server only, which writes 127.0.0.1:<host port>:<port>. Docker-published ports are not filtered by ufw.
  • A port clash with an application, database, another stack, or any container that's currently running is rejected when you save — not at up time, so an existing stack is never torn down for nothing.
  • Access changes only take effect after a redeploy; the panel shows a reminder.
  • Deploying from a template also accepts host ports directly in the dialog.

Domain per service

  1. Domain card on the stack page

    Pick a Service, the Port inside the container, a Hostname, and toggle HTTPS.

  2. Redeploy

    On deploy, aoox writes an override file docker-compose.aoox.yml next to the compose file, containing the Traefik label (router <slug>-<service>-<port>) and the aoox + default networks, then runs -f compose -f override.

  • New domain changes only take effect after a redeploy.
  • Stop/start use the override already sitting in the volume, so they stay consistent with the running stack.
  • With no domain, there's no override — the compose file runs as-is.

Operations

ActionWhat runsNotes
Deployup -d --build --remove-orphansRe-clones the branch, builds the image if there's a build:.
StopstopContainers stop, volumes are kept.
StartstartNo clone/build.
Deletedown --volumes --remove-orphans + delete the checkout volumeThe stack's volume data is deleted too.

History, webhooks & metrics

Every time the compose runner runs — Deploy, Stop, Start, Delete, or via a webhook — it's recorded as one history row (action, trigger, logs, commit). A stack is N containers, so this is a history of actions, not an image/rollback history like an application's Deployment.

  • Auto-deploy on push: enable the Webhook on the Deploy tab to get a URL containing a token. The contract is the same as an application's webhook — an optional GitHub/GitLab signature, and irrelevant pushes still get a 200 so the provider doesn't disable the hook. Stacks from a template have no repo, so pushes are accepted but ignored.
  • Metrics & container-down notifications now work — stack containers are recognized by the compose project name (aoox-<slug>), not an aoox component label. Notifications stay quiet while a run is in progress, since up -d --build stops and restarts every container.
  • Resource limits still can't be set from the UI — use deploy.resources in the compose file.

Behavior & limitations

  • build: is fully supported — the compose CLI uses BuildKit (unlike Nixpacks applications).
  • No rollback or health check/blue-green per stack — Deploy always runs up -d --build again, rather than swapping images like an application does.
  • No mounts from the UI for stacks — configure volumes in the compose file.
  • Jobs are available under the stack's Jobs tab: pick a Service, then exec inside that service's container or a separate container from its image — see Scheduled jobs.
  • Databases inside a stack don't get aoox's backup features — for important data, use a managed database.
  • A compose file from a repo may request privileged / host mounts — treat the repo like code running on the host.
  • A failed deploy sends a deployment-failure notification.

Common pitfalls

SymptomCause & fix
A service doesn't see the envThe stack env is only for interpolation. Add environment: on the service.
Bind mount ./file fails: not a directoryAlready handled (checkout happens at the volume's mountpoint). If it still fails, relative paths are resolved from the compose file's folder, not the repo root.
Services can't find each other after adding a domainThe override already includes the default network. If your compose file declares its own networks: on a service, include default.
Can't connect to the managed databaseThe service isn't on the aoox network. Add a domain (automatic) or an external network manually.
Domain returns 404Not redeployed yet after adding the domain, or the service port is wrong.
Host port rejected when savingThe port is already used by an application, database, another stack, or a running container. Pick another port.
Push doesn't trigger a deployA stack from a template has no repo — pushes are always ignored. For a stack from a repo, check the webhook URL and the provider's signature.

Template

A stack from the catalog (source: template) uses the same runner, but its compose content is stored in aoox and can be edited from the UI without a repo — see One-click templates.

Next steps

Found a mistake? Edit this page on GitHub ↗