VACVAC
Apps

Build settings

How VAC detects, builds, and runs your app — and how to take control when detection isn't enough.

3 min readUpdated June 16, 2026Edition · v0.5.xBeta

Whatever your repo looks like, VAC normalizes it into a Docker Compose stack that it builds and runs. The component that does that translation is the build adapter, and you choose which one via the app's build kind.

Build kinds

KindWhat it does
auto (default)Detect the right kind from the repository contents.
composeUse a Compose file in the repo as-is.
dockerfileWrap a standalone Dockerfile in a generated single-service Compose stack.
frameworkDetect a supported framework (React today; more on the way) and generate a Compose stack that builds it.
staticServe a static directory behind a tiny file server, with optional SPA fallback.

Auto-detection order

With auto (the default), VAC checks, in order:

  1. Composecompose.yaml, compose.yml, docker-compose.yml, or docker-compose.yaml.
  2. Dockerfile — a Dockerfile at the repo root → generated wrapper.
  3. Framework — a package.json that depends on React. (React is the only framework detected today; more are on the way.)

If none match, the deploy fails with a clear "couldn't detect how to build this" error — set the build kind explicitly to resolve it.

Build configuration

Each kind takes a small config object you can set on the app:

  • compose — alternate Compose file path.
  • dockerfile — alternate Dockerfile path.
  • framework — framework name (react today) and an optional build command (defaults to npm install && npm run build).
  • static — source directory, and whether to serve index.html for unmatched routes (SPA fallback).
Compose stays the substrate

Even Dockerfile, framework, and static apps end up as a Compose file under the hood — the rest of the pipeline (preflight, routing, health, limits) is identical across kinds.

Preflight: what VAC checks before building

After resolving your Compose (via docker compose config, so includes and overrides are expanded), VAC lints it. Some findings block the deploy; others are warnings that let it proceed.

Blocking errors

These conflict with how VAC owns the edge and keeps the control plane sandboxed:

IssueWhy it's blocked
Publishing host port 80 or 443Caddy (vac-proxy) owns those ports.
A bundled reverse proxy (Traefik, Caddy, nginx-with-ACME)VAC already fronts your app with Caddy.
Mounting the Docker socketThat grants host-root from inside an app container.
privileged, network_mode: host, or dangerous cap_addBreaks the sandbox boundary.
Override, carefully

An allow_unsafe_compose escape hatch downgrades the edge-port conflicts to warnings — but it never lets through the host-escape cases (Docker socket, privileged, host networking). Those stay blocked.

Warnings (deploy proceeds)

  • Publishing a non-80/443 host port — works, but bypasses Caddy and HTTPS.
  • A fixed container_name — breaks VAC's project scoping; the service won't be routed.
  • A lifecycle daemon (Watchtower/Ouroboros) — VAC owns image and container lifecycle.
  • No routable HTTP service — nothing exposes a port, so no domain can be assigned. Fine for a worker-only app.

Services and ports

A service becomes HTTP-routable when VAC can determine its container port. After bringing the stack up, VAC inspects the running containers:

  1. If the service publishes a port, that target port is used.
  2. Otherwise, the first expose: entry is used.
  3. If neither exists, the service has no route and no health check — it runs as a background worker.

You can adjust two things per service from the dashboard:

  • Internal port — the container port Caddy dials. Useful when a service only exposes a port and detection guesses wrong.
  • Health path — the path Caddy's active health check hits (default /). See Health checks.

Changing the internal port or health path re-syncs Caddy immediately — no redeploy needed.

Memory limits

You can cap an app's per-container memory from its settings; VAC writes a Compose override (mem_limit) on the next deploy so a runaway container can't OOM the whole box. See Resource limits.

Next

On this page