Deploy (Zap Arc)
Deploy a zap project to the cloud from a deploy: block in zap.yaml — on your own cloud accounts.
Zap Arc deploys a zap project to the cloud from a top-level deploy: block in
zap.yaml. The pitch in one line: your zap.yaml, deployed — a render.yaml
that's deployable, on your own cloud accounts.
Arc is opt-in: projects without a deploy: block are unaffected. Under the
hood it is Zapper's Terraform integration — manifest in, Terraform + lifecycle
out. The engine is provider-agnostic: all provider knowledge (GCP, Vercel,
Cloudflare, …) lives in data — a network config you control and a module
library — never in engine code.
Prerequisites
Arc's current runner uses the local Terraform CLI for plan, deploy, and
destroy. Install terraform so it is available on PATH; those commands
fail early with an install hint when the binary is missing.
Operators who want reproducible tool versions can pin Terraform in mise
(mise.toml) and expose it through mise shims on PATH.
Commands
zap arc plan # render Terraform from the deploy block and show the plan
zap arc deploy # build/push images, apply Terraform, run hooks
zap arc destroy # terraform destroy of everything in state
zap arc bootstrap # one-time network setupzap arc deployruns pre-apply hooks, applies project-level modules, builds and pushes container images, applies Terraform against central state, then runs post-apply hooks.--keepretains the rendered Terraform on failure.zap arc destroyremoves everything in state. If the deploy block includes agcp-projectmodule, this deletes the project's GCP project too (30-day recovery window).zap arc bootstrapis the one-time setup for a new network: the network-level GCP project, the Terraform state bucket, and docker auth. It is the one GCP-aware convenience in the engine, and needsnetwork-projectandregiontemplate vars and agcsbackend in the network config.
Three configuration layers
- Project — the
deploy:block inzap.yaml(below). Committed with the code; says what the project deploys. - Network — a
network.yamldescribing your cloud footprint: Terraform backend, providers, image registry template, DNS zone, module defaults, env resolver. This is the sharing seam — one network config serves every project you deploy. See the annotated example. - Operator —
~/.config/zap-arc/config.yaml, a two-line pointer:network:(path to the network config) andcredentials:(a dotenv of provider tokens). Override withzap arc --configorZAP_ARC_CONFIG. GCP auth comes from gcloud application-default credentials.
The deploy block
deploy:
project: # project-level modules: no service semantics
gcp:
module: gcp-project
registry:
module: project-base
depends-on: [gcp]
services:
backend:
module: cloud-run-web
domain: api.example.com
port: 8080 # any non-reserved key passes through to Terraform
min-instances: 1
env:
- SOME_SECRET # bare KEY: whitelisted from the pool
- PUBLIC_URL=https://example.com # KEY=value: committed literalParams are pure pass-through. Every key that isn't reserved flows to the
module's Terraform variables verbatim (kebab→snake on the key). The manifest
is the module's variables.tf: unknown params fail at terraform plan, and
defaults live in the module. The reserved structural keys are arc's own:
module, domain, env, dockerfile, build, deploy-path,
remote-build, local-config, vercel-name, depends-on.
Setting domain passes domain and dns_zone (the network's zone name) to
the module — each module does its own zone lookup.
deploy.project entries are modules without service semantics (no env map,
no container, no URL) — project factories, registries, budgets, DNS.
depends-on: [<entry key>] orders them; arc applies all project modules
before images push and services apply.
Service env and the pool
Service env is one env: list. Bare names are whitelisted from a pool;
KEY=value entries are literals committed inline. The pool is piped in on
stdin (JSON or dotenv):
<your secrets tooling> | zap arc deployor comes from the network config's fallback resolver command. Binding
modules (shared databases, redis, buckets) inject their env vars via their
module.yaml — injections need no declaring, and they fill gaps only: a
service's own env entry wins on conflict, so divergence stays declared in the
deploy block.
Modules
A module is a Terraform folder, plus an optional module.yaml for the things
Terraform can't declare: a container build (action: container), default
params, required operator credentials, env vars to inject into sibling
services, and pre/post-apply hooks. A bare Terraform folder is a valid
module.
module: resolves three ways:
- Bare slug (
cloud-run-web) — the module library bundled with the zap package. A network config can pointmodules:at its own library instead. - Terraform source URL (
github.com/org/repo//mod?ref=v1) — passed through as the Terraform module source; Terraform fetches, caches and pins it. Remote modules never mean remote code execution beyond their declared hook commands. ./relative— resolved against the project dir: the raw-Terraform escape hatch and one-off plugins.
The bundled library includes cloud-run-web (public HTTP container service),
gce-worker (always-on container worker), vercel-static (static frontend),
gcp-project and project-base (per-project GCP project + registry),
gcp-budget, posthog-proxy, shared-mongo-atlas, upstash-redis, and
aws-s3 (private bucket + scoped IAM user; optional cors-origins for browser
requests using presigned URLs).
shared-mongo-atlas grants one scoped Atlas user access to its primary
database and injects MONGODB_URL into container services. Set optional
content_database, admin_database, or user_database parameters when an
application separates those roles. The module grants access to each selected
database and also injects CONTENT_DB_CONNECTION_URL,
ADMIN_DB_CONNECTION_URL, and USER_DB_CONNECTION_URL; omitted roles use the
primary database. It manages credentials, not the cluster or collection data.
Network config
backend: and providers: in network.yaml are passthrough maps rendered
straight into the Terraform backend/provider blocks. Provider configs may use
{{cred.NAME}} from the operator credentials file, and any string may use
{var} template variables — {slug}, {network}, plus every extra
top-level string key the network config defines (e.g. region). registry:
is the image template arc appends /<service>:<tag> to, and
module-defaults: feeds network facts (regions, billing accounts, cluster
hosts) to modules without touching project files.
Start from the
example network config:
copy it somewhere you control, point ~/.config/zap-arc/config.yaml at it,
run zap arc bootstrap once, then zap arc deploy per project.
For the full contract — module.yaml fields, hooks, template scopes, param merge order — see the arc reference in the repo.