Skip to main content

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:

FieldRequired?Purpose
nameyesUnique identifier; used in URLs, commands (gws logs <name>), and profile overrides.
pathyesFilesystem path to the service's source. Relative to gws.json.
portfor routed servicesContainer 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.

FieldDefaultPurpose
dockerfile<path>/DockerfilePath to the Dockerfile used to build the image.
manifests<path>/k8s/Folder of Kubernetes manifests applied alongside the deployment.
imageauto-generatedOverride the built image name (skips the build, useful for pinning a published image).
buildArgsMap 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.

FieldDefaultPurpose
portContainer 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>.
routesPath-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.

FieldDefaultPurpose
fileSynctrueBidirectional sub-second sync between your local source and the running pod. Set false for static images, databases, or e2e runs.
fileSyncIgnoreGlob patterns excluded from sync (e.g. ["node_modules/", "dist/", "*.pyc"]).
watchRules 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

FieldDefaultPurpose
enabledtrueWhen 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.

path is 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.devweb
  • https://api.my-project.local.getwebstack.devapi
  • mysql://localhost:3306mysql pod (via expose)
  • worker runs in the cluster but isn't routed.

See also