Reference

Architecture

Astrodock is one docker compose stack: five services that together route, run, authenticate, store, and monitor your apps on a single box. This page shows how the pieces fit and how a request flows through them.

The stack at a glance

                          ┌──────────────── host / VPS ────────────────┐
   *.<base-domain> ─TLS──▶ │  caddy        reverse proxy + auto-HTTPS      │
   (the only public ports) │    │           serves static frontends (vol)  │
                           │    │           proxies /api/* and Docker apps  │
                           │    ▼                                          │
                           │  api          control plane (Express)         │
                           │    │           admin API · /verify · webhooks  │
                           │    │           health monitor · orchestration  │
                           │    ▼  (internal, token-authed)                 │
                           │  runner       clones, builds, runs apps        │
                           │   │  holds the Docker socket + GitHub PAT      │
                           │   ├─ PM2 ───▶ node app  <slug>  (buildpack)    │
                           │   └─ docker ▶ app-<slug> container (Dockerfile) │
                           │                                               │
                           │  postgres     bundled DB (control plane + apps)│
                           │  objectstore  bundled S3-compatible storage    │
                           └───────────────────────────────────────────────┘
   volumes:  pgdata · objectdata · static (builds) · apps (server code) · repos (clone cache)
   network:  one bridge net; Caddy resolves app containers by name; runner reachable as "runner"

What each service does

ServiceRole
caddy Reverse proxy and automatic HTTPS. The only thing on ports 80/443. Serves built static frontends from the shared static volume, proxies /api/* to Node apps, and whole-proxies Dockerfile-app subdomains.
api The control plane (Express). Hosts the admin API and dashboard, the /verify end-user auth endpoint, GitHub webhooks, the health monitor, and deploy orchestration. Talks to postgres. Holds no Docker socket and no GitHub PAT.
runner Clones, builds, and runs your apps. Runs Node buildpack apps as per-app non-root OS users via PM2 (inside the runner), and Dockerfile apps as sibling containers. Holds the Docker socket and the GitHub PAT. Reachable only on the internal network, token-authed.
postgres The bundled database — both the control-plane store and the internal per-app database option.
objectstore The bundled S3-compatible object storage (SeaweedFS) — the internal per-app storage option. Internal network only.

How a request flows

Everything from the outside world enters through Caddy. Where it goes next depends on the host and the path:

The api / runner split (and why)

The api and runner are separate containers. This is deliberate, and it's the platform's main internal trust boundary.

The runner is the powerful, dangerous part: it holds the Docker socket (which is roughly root on the host) and the GitHub PAT, and it executes code from your repos when it builds and runs apps. The control plane — the thing that serves the admin API and /verify — holds neither the socket nor the PAT. Keeping the build/exec surface in its own container isolates that risk away from the API.

The two talk over the internal compose network only, and the runner's internal API is token-authed with ASTRODOCK_RUNNER_TOKEN. Nothing outside the box reaches the runner directly.

Two roles, one image

The api and runner are built from the same image, started in different roles (ASTRODOCK_ROLE). What differs is what each one is handed: the runner gets the socket, the PAT, and the build volumes; the api gets neither.

Where data lives

State is kept in named Docker volumes, so it survives restarts and upgrades:

VolumeWhat's in it
pgdataPostgres data — control-plane records and internal app databases.
objectdataThe bundled object store's contents (internal app buckets).
staticBuilt frontends (and the admin SPA), served by Caddy.
appsServer code for running Node buildpack apps.
reposThe runner's clone cache for connected repos.

Back these up

For a real install, the volumes that hold your data are pgdata and objectdata. See Backups & restore.

The deploy & provision flow

At a high level, a deploy goes:

  1. Trigger

    A webhook from a git push (via api), or astrodock deploy. A deploy record is created so progress is observable.

  2. Provision & gate

    Resources are ensured (an internal Postgres DB + role, or an internal storage bucket + scoped key), and the required-variable gate runs — if any required secret or external credential is missing, the deploy is rejected before any code runs.

  3. Build

    The runner clones the repo, then builds. Node apps: install deps and run the buildCommand; the frontend's dist/ is synced to the static volume. Dockerfile apps: docker build an image.

  4. Run

    Node apps start as a PM2 process bound to ASTRODOCK_PORT; Dockerfile apps run as a sibling container on the compose network. Caddy routes the subdomain accordingly.

  5. Health probe

    The runner probes the app (a GET /health 200 means healthy) and marks the deploy success or failed, with the full streamed log attached.

The injected environment for the app process is the reserved ASTRODOCK_* set (computed from the resource modes) plus the app's declared vars — the platform's own stack secrets are never injected into an app.

Related