Reference

synq.json

The declarative deploy config for Synq — like vercel.json or railway.toml. Declare framework, build, runtime, env, domains, routes, resources, services and monorepo layout, and Synq deploys the same way every time.

synq.json is a repo-root file that tells Synq how to build, run, route, and scale a project. It mirrors what you'd otherwise set in the deploy wizard, so a repo becomes "ready to ship on Synq" and auto-deploy on push and synq deploy become deterministic — no wizard needed.

Authoritative overlay

Synq auto-detects your stack first, then applies every field present in synq.json on top. Fields you omit keep their detected value. So a good synq.json is small — declare only what you want to override or pin, not the whole detected config.

Add the $schema line for editor autocomplete and inline validation:

{
  "$schema": "https://synquic.tech/synq.schema.json",
  "framework": "nextjs",
  "port": 3000
}

Validate it any time with the CLI:

synq config init       # scaffold a starter synq.json
synq config validate   # check it against the schema

It's JSON, not JSONC — no comments, no trailing commas.

Build

Prop

Type

Runtime

Prop

Type

Persistent storage

Everything a container writes is discarded when the next version replaces it. volumes names the paths to keep.

A bare path is relative to the app; full compose syntax works too, so you can point a mount at a specific volume name or a host directory:

{
  "volumes": ["storage", "uploads:/app/public/uploads", "/srv/data:/app/var"]
}

Omit the field to inherit the framework's own defaults (a Laravel app keeps storage/ with no configuration). Declare [] to turn persistence off entirely — that is different from omitting it.

Named volumes are scoped per project, so two projects that both declare storage never share one. On a bare (non-container) deploy the same paths are kept outside the release directory and symlinked in.

Compose services declare their own mounts under services[].volumes.

Environment variables

env is an object. A value is a plain string, or { "value", "secret" } to mark a secret (encrypted at rest — never returned to the client afterward). These declarations are active automatically; a value saved in the dashboard or CLI for the same key overrides the repository default.

{
  "env": {
    "NEXT_PUBLIC_URL": "https://app.acme.com",
    "DATABASE_URL": { "value": "postgres://…", "secret": true }
  }
}

Domains

domains is an array of hostnames or objects. A bare label ("myapp") asks for a free subdomain, which on a Synq install is myapp.<your base domain> (SYNQ_APPS_BASE_DOMAIN); a dotted hostname ("app.acme.com") is a custom domain.

{ "domains": ["app.acme.com", { "domain": "api.acme.com", "port": 8080, "type": "custom" }] }

Prop

Type

Routes

routes reproduces vercel.json-style routing, compiled to the reverse proxy at deploy.

Prop

Type

Resources

CPU/memory limits for the project's containers — a named tier, or explicit values (which become the custom tier). See Sleep mode & resources.

Self-hosted defaults to no limits

On a self-hosted instance the default is unlimited — the machine is the ceiling, because it's your hardware. Set a tier or explicit values only when you deliberately want to cap a container. A non-zero value is validated against the target machine's real capacity, so a 64 GB box can hand a container 64 GB.

Prop

Type

{
  "resources": { "cpuCores": 4, "memoryMb": 8192 }
}

Services (compose)

Declaring services makes the project a multi-service (Docker) project — see Compose / multi-service. Each entry requires a name.

Already have a compose file? Don't re-declare it here — point composePath at it and leave services out. That works for a compose file anywhere in the repo (deploy/docker-compose/), under any filename (stack.yml), and keeps the file the single source of truth: its services are re-read on every deploy, and build: contexts resolve relative to it, exactly as compose does.

{ "composePath": "deploy/docker-compose/docker-compose.yml" }
{
  "services": [
    {
      "name": "web",
      "build": ".",
      "buildArgs": { "NEXT_PUBLIC_API_URL": null },
      "ports": ["3000"],
      "exposed": true,
      "domain": "app.acme.com"
    },
    {
      "name": "db",
      "image": "postgres:17",
      "volumes": ["pgdata:/var/lib/postgresql/data"],
      "env": { "POSTGRES_PASSWORD": { "value": "…", "secret": true } },
      "restart": "unless-stopped"
    }
  ]
}

Prop

Type

dependsOn takes service names such as ["db"], without a port. It controls container startup order, not database availability during an image build. For ENOTFOUND db during next build, see Database access during a Next.js build.

Top-level env is available to Dockerfile builds as well as running containers. Use buildArgs when a service needs an explicit override or when you want the config to document which Dockerfile arguments it consumes. A service's own env is runtime-only and is never shared with sibling builds. Keep runtime-only credentials in service env where possible: Docker build arguments are not a secret-storage mechanism and can be retained in image metadata or build cache.

A compose file's own limits are honored too — Synq reads mem_limit / cpus and deploy.resources.limits.{memory,cpus} off each service and applies them over the project-wide setting.

Monorepo

monorepo overrides Synq's detected sub-apps. Entries in apps[] are matched to detected sub-apps by rootDirectory and override their build settings — they don't declare apps from scratch (the detector finds the apps).

Each override must have a different normalized rootDirectory. Declaring a web process and a worker at the same root is not a supported multi-app declaration. synq config validate rejects duplicate roots; source scans report unmatched apps and workspace overrides in the configuration diagnostics.

Create a monorepo through the repository import flow, which supplies the detected app metadata. The API/SDK also accepts that metadata as monorepoApps. Setting projectType: "monorepo" (or CLI project create --type monorepo) without it now fails before creating a project. A local CLI import scans the workspace first. For independent processes without containers, use separate projects pointed at the required roots and start commands.

{
  "monorepo": {
    "workspace": { "packageManager": "pnpm", "prepareCommand": "pnpm install && pnpm codegen" },
    "apps": [
      { "name": "web", "rootDirectory": "apps/web", "framework": "nextjs", "port": 3000 },
      { "name": "api", "rootDirectory": "apps/api", "framework": "hono", "port": 8080 }
    ]
  }
}

Prop

Type

Not supported yet

sleepMode, monorepo sharedPaths, and per-app domain/env/exposed are accepted by the validator but not applied — leave them out. Set sleep mode and per-app domains in the dashboard for now.

How it maps

synq.jsonDeploy wizard / API
framework, packageManager, *Command, outputDirectory, buildImage, productionPaths, rootDirectoryBuild settings
port, productionMode, runtimeRuntime settings (hasServer, runtime isolation)
volumesConfiguration → Persistent storage
envEnvironment variables (seeded as editable rows)
domainsPublic endpoints / custom domains
routesReverse-proxy routing (routingConfig)
resourcesMachine Power (resource tier / custom sizing)
servicesCompose services
monorepoMonorepo sub-apps + workspace

See also

On this page