Build settings
How VAC detects, builds, and runs your app — and how to take control when detection isn't enough.
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
| Kind | What it does |
|---|---|
auto (default) | Detect the right kind from the repository contents. |
compose | Use a Compose file in the repo as-is. |
dockerfile | Wrap a standalone Dockerfile in a generated single-service Compose stack. |
framework | Detect a supported framework (React today; more on the way) and generate a Compose stack that builds it. |
static | Serve a static directory behind a tiny file server, with optional SPA fallback. |
Auto-detection order
With auto (the default), VAC checks, in order:
- Compose —
compose.yaml,compose.yml,docker-compose.yml, ordocker-compose.yaml. - Dockerfile — a
Dockerfileat the repo root → generated wrapper. - Framework — a
package.jsonthat 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
Dockerfilepath. - framework — framework name (
reacttoday) and an optional build command (defaults tonpm install && npm run build). - static — source directory, and whether to serve
index.htmlfor unmatched routes (SPA fallback).
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:
| Issue | Why it's blocked |
|---|---|
| Publishing host port 80 or 443 | Caddy (vac-proxy) owns those ports. |
| A bundled reverse proxy (Traefik, Caddy, nginx-with-ACME) | VAC already fronts your app with Caddy. |
| Mounting the Docker socket | That grants host-root from inside an app container. |
privileged, network_mode: host, or dangerous cap_add | Breaks the sandbox boundary. |
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:
- If the service publishes a port, that target port is used.
- Otherwise, the first
expose:entry is used. - 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.