Services
A service in gws.json is one deployable unit of your project — your API, your frontend, a worker, a database, anything that runs in a pod. The services array is the heart of gws.json; almost every other field exists to support it.
{
"name": "my-project",
"services": [
{ "name": "api", "path": "./api", "port": 3000 },
{ "name": "web", "path": "./frontend", "port": 80 }
]
}
A service in one line
The minimum a service needs:
| Field | Required? | Purpose |
|---|---|---|
name | yes | Unique identifier; used in URLs, commands (gws logs <name>), and profile overrides. |
path | yes | Filesystem path to the service's source. Relative to gws.json. |
port | for routed services | Container port the Gateway forwards traffic to. Required if the service receives HTTP traffic. |
Everything else is optional and has sensible defaults.
Build inputs
How GetWebstack turns your source into a running container.
| Field | Default | Purpose |
|---|---|---|
dockerfile | <path>/Dockerfile | Path to the Dockerfile used to build the image. |
manifests | <path>/k8s/ | Folder of Kubernetes manifests applied alongside the deployment. |
image | auto-generated | Override the built image name (skips the build, useful for pinning a published image). |
buildArgs | — | Map passed as --build-arg to docker build. Handy for selecting an entrypoint via Dockerfile arg. |
{
"name": "api",
"path": "./api",
"dockerfile": "./api/Dockerfile.dev",
"manifests": "./api/k8s/dev",
"buildArgs": { "BUILD_SCRIPT": "build:ssr:dev" }
}
Routing & networking
How the service is reached.
| Field | Default | Purpose |
|---|---|---|
port | — | Container port the Gateway forwards to. Required for any service that receives HTTP traffic. |
subdomain | <name> | Subdomain used in the public URL. With subdomain: "api" the service lives at https://api.<project>.<domain>. |
routes | — | Path-based routing: this service becomes a router that forwards specific paths to other services. |
expose | { enabled: false } | Bind the service to a localhost port for direct access (skips the Gateway). |
Default URL pattern: https://<subdomain or name>.<project>.<domain>. The same hostname serves every worktree of the project — the gws-namespace cookie picks which fork's pods receive the request. The picker UI at https://<project>.<domain> lets you choose; gws status -w <wt> --json returns the deployment ID to set programmatically.
Path-based routing
A "gateway service" can fan out paths to other services:
{
"name": "edge",
"path": "./edge",
"port": 80,
"subdomain": "app",
"routes": [
{ "path": "/api", "service": "api" },
{ "path": "/blog", "service": "blog" }
]
}
https://app.<project>.<domain>/api/... → api service. https://app.<project>.<domain>/blog/... → blog service.
Exposing a service on localhost
{
"name": "postgres",
"path": "./db",
"port": 5432,
"expose": { "enabled": true, "localPort": 5432, "autoRecover": true }
}
Now psql -h localhost -p 5432 reaches the running pod, no port-forward needed.
File sync & watch rules
How the service stays in step with your editor.
| Field | Default | Purpose |
|---|---|---|
fileSync | true | Bidirectional sub-second sync between your local source and the running pod. Set false for static images, databases, or e2e runs. |
fileSyncIgnore | — | Glob patterns excluded from sync (e.g. ["node_modules/", "dist/", "*.pyc"]). |
watch | — | Rules that trigger rebuilds, manifest re-applies, or custom scripts when matching files change. See Watch Configuration. |
{
"name": "api",
"path": "./api",
"port": 3000,
"fileSync": true,
"fileSyncIgnore": ["node_modules/", "dist/", "__pycache__/"],
"watch": [
{ "patterns": ["Dockerfile", "package*.json"], "actions": ["rebuildImage"] },
{ "patterns": ["k8s/**"], "actions": ["reapplyK8sManifest"] }
]
}
Lifecycle
| Field | Default | Purpose |
|---|---|---|
enabled | true | When false, the service is skipped entirely — not built, not deployed. Most often used inside profiles to disable services for ci, e2e, etc. |
{ "name": "docs", "path": "./docs", "enabled": false }
Helm services
For services you'd rather deploy from a Helm chart than from a custom Dockerfile + manifests:
{
"name": "mysql",
"path": "./mysql",
"port": 3306,
"helm": {
"chart": "bitnami/mysql",
"version": "11.1.2",
"repositories": [
{ "name": "bitnami", "url": "https://charts.bitnami.com/bitnami" }
],
"values": {
"auth": { "rootPassword": "${MYSQL_ROOT_PASSWORD}" }
},
"wait": true,
"timeout": "300s"
}
}
When helm is set, dockerfile, manifests, image, fileSync, and watch are ignored.
pathis still required by the schema even for Helm-only services. Any non-empty string works (e.g."./mysql"); nothing on disk is read.
Worked example
A typical full-stack project — frontend, API, worker, managed database:
{
"name": "my-project",
"version": "1.0.0",
"services": [
{
"name": "web",
"path": "./frontend",
"port": 80,
"subdomain": "app",
"fileSyncIgnore": ["node_modules/", "dist/"]
},
{
"name": "api",
"path": "./api",
"port": 3000,
"subdomain": "api",
"watch": [
{ "patterns": ["Dockerfile", "package*.json"], "actions": ["rebuildImage"] }
]
},
{
"name": "worker",
"path": "./api",
"dockerfile": "./api/Dockerfile.worker",
"fileSync": true
},
{
"name": "mysql",
"path": "./mysql",
"port": 3306,
"expose": { "enabled": true, "localPort": 3306 },
"helm": {
"chart": "bitnami/mysql",
"version": "11.1.2",
"repositories": [{ "name": "bitnami", "url": "https://charts.bitnami.com/bitnami" }]
}
}
]
}
URLs after gws up:
https://app.my-project.local.getwebstack.dev→webhttps://api.my-project.local.getwebstack.dev→apimysql://localhost:3306→mysqlpod (viaexpose)workerruns in the cluster but isn't routed.
See also
gws.jsonoverview — top-level structure and lifecycle- Profiles — override service fields per scenario (
ci,e2e,staging) - Watch Configuration — full reference for
watchrules WebstackServiceschema — authoritative TypeScript interface