skiff2.json Reference
skiff2.json lives at the root of your repo and describes the services you want to build and deploy.
Example
{
"$schema": "https://storage.googleapis.com/skiff2-general/skiff2-config.schema.json",
"services": [
{
"name": "ui",
"cwd": "./ui",
"isRootService": true,
"allowUnauthenticated": true
},
{
"name": "api",
"cwd": "./api",
"secretFiles": {
"my-secret-name": "/secrets/config.json"
}
}
]
}
Top-level fields
services (required)
Array of service definitions. At least one service is required.
Type: ServiceConfig[]
remoteServices
An optional array of remote services that are deployed from repositories other than the main repository for a project.
Type: RemoteServiceConfig[]
projectName
An optional override for the project name used when naming Cloud Run services and images. Defaults to the repository name.
Type: string
environments
An optional list of environment names that this project deploys to.
Type: string[]
prodBranch
The branch whose deploys map to the prod environment. Deploys from this branch use the bare domain, and can route custom domains. It is recommended to not change this once set. The deploy action will run into issues when this is changed and it will require some manual config to fix the deploy.
Type: string
Default: "main"
ServiceConfig fields
name (required)
A short identifier for this service. Used to derive:
- The Cloud Run service name
- The container image name
- The subdomain for non-root services (e.g.
prod-api.my-project.apps.allenai.org)
Type: string
cwd (required)
Path to the directory containing the Dockerfile, relative to the skiff2.json file.
Type: string
Example: ".", "./ui", "./packages/server"
isRootService
When true, this service is routed from the project's top-level domains:
my-project.pandajungle.orgmy-project.allen.aimy-project.apps.allenai.org
Only one service should have isRootService: true. Non-root services get their own subdomain prefix (e.g. prod-api.my-project.apps.allenai.org).
Type: boolean
Default: false
dockerFile
Path to the Dockerfile, relative to cwd. Defaults to Dockerfile in the cwd directory.
Type: string
allowUnauthenticated
Allows unauthenticated requests to this Cloud Run service. Required for public-facing web apps.
Type: boolean
Default: false
allowedPrincipals
GCP IAM members allowed to access this service through IAP. IAP still forces a Google sign-in; this list controls who passes the check after they've signed in.
Type: string[]
Default: ["domain:allenai.org"]
Each entry is a literal GCP IAM member identifier. Supported forms:
| Form | Example | Meaning |
|---|---|---|
domain: | "domain:allenai.org" | Anyone with an email in this Google Workspace domain |
user: | "user:alice@example.com" | A specific user |
group: | "group:partners@allenai.org" | A Google Group |
allAuthenticatedUsers | "allAuthenticatedUsers" | Anyone signed in with any Google account, including personal @gmail.com |
Example (mix a domain with a few outside users):
{
"allowedPrincipals": [
"domain:allenai.org",
"user:contractor@example.com",
"group:partners@allenai.org"
]
}
This field is ignored when allowUnauthenticated: true (the service is fully public in that case). To make a service public, use allowUnauthenticated, not "allUsers" here — "allUsers" is rejected by the schema.
allAuthenticatedUsers is broader than it soundsallAuthenticatedUsers lets in any Google account on the internet, including personal @gmail.com addresses — not just AI2 users. In most cases you want domain:allenai.org plus specific user: or group: exceptions.
allAuthenticatedUsers needs a one-time manual OAuth setupIAP's default Google-managed OAuth client only permits accounts inside the AI2 org, so allAuthenticatedUsers won't take effect until you switch the service to a custom OAuth client. This is a manual step in the GCP console (Terraform can't do it):
- Open the Google Auth overview for your project and fill out the branding form (app name + support contact email).
- Open Identity-Aware Proxy, find your service in the list, click the ⋮ menu → Settings, and change Google-Managed OAuth to Custom OAuth.
Add ?project=YOUR_PROJECT_ID to either link to open your project directly. See the Authentication guide for details.
serviceAccount
The GCP service account that this Cloud Run service runs as. Applies to the main container and all sidecars in the service. When omitted, Cloud Run uses the default compute service account.
Type: string (email)
Format: <ACCOUNT_NAME>@<PROJECT_NAME>.iam.gserviceaccount.com
Example:
{
"serviceAccount": "my-api-runner@my-project.iam.gserviceaccount.com"
}
A freshly created service account starts with no IAM bindings. The default Cloud Run service account works out of the box because it has broad project-level access — a custom one will not. If you switch to a new service account, you'll need to grant it access to anything the service uses (Secret Manager secrets, GCS buckets, Cloud SQL, Pub/Sub, etc.) or runtime calls will fail with permission errors.
allowDelete
Allows this Cloud Run service to be deleted by Terraform. When false, the service is excluded from the load balancer URL map to prevent deletion errors.
Type: boolean
Default: false
httpVersion
The HTTP version to use for this service. When set to "2", the service uses end-to-end HTTP/2 (h2c) between Cloud Run and your container.
Type: "1" | "2" (string)
Default: "1"
Example:
{
"httpVersion": "2"
}
Only use HTTP/2 if your service actually supports it. With httpVersion: "2", Cloud Run sends requests to your container over HTTP/2 cleartext (h2c) — your server must speak h2c (HTTP/2 without TLS) or requests will fail. This is typically needed for gRPC or streaming workloads.
secretFiles
Mounts Secret Manager secrets as files inside the container.
Type: Record<string, string>
The key is the Secret Manager secret name; the value is the mount path inside the container.
Example:
{
"secretFiles": {
"my-app-config": "/secrets/config.json",
"my-app-credentials": "/secrets/credentials.json"
}
}
ephemeralStorage
Mounts Cloud Run ephemeral disk volumes inside the container. Useful for scratch space like caches or temporary downloads that don't fit in memory.
Type: Record<string, string>
The key is the mount path inside the container; the value is the provisioned size. Sizes use the 10Gi format and can be at most 10Gi per volume.
Example:
{
"ephemeralStorage": {
"/tmp/cache": "5Gi",
"/data/scratch": "10Gi"
}
}
Each instance gets its own empty volume, and the data persists only for the lifetime of that instance. When an instance is shut down (scale-down, redeploy, etc.), its data is gone. Do not use this for anything you need to keep.
You are billed for the full provisioned size for the lifetime of each instance, regardless of how much is actually used. Ephemeral disk is also a Pre-GA Cloud Run feature — services that use it are deployed with the BETA launch stage and the gen2 execution environment; all other services stay on GA.
nfsVolumes
Mounts NFS volumes (e.g. a Filestore instance) inside the container. Unlike ephemeralStorage, an NFS share is shared across all instances and persists independently of them.
Type: Record<string, object>
The key is the mount path inside the container; the value is an object describing the NFS share:
| Field | Type | Default | Description |
|---|---|---|---|
server | string | - | Hostname or IP of the NFS server (e.g. a Filestore instance IP). |
path | string | - | The exported path on the NFS server to mount. |
readOnly | boolean | false | Whether to mount the share read-only. |
Example:
{
"nfsVolumes": {
"/mnt/data": {
"server": "10.0.0.5",
"path": "/exports/data"
},
"/mnt/shared": {
"server": "10.0.0.5",
"path": "/exports/shared",
"readOnly": true
}
}
}
The NFS server must be reachable from the service, which usually means configuring vpc so the service can reach the private IP of the share. Services that use NFS volumes are deployed with the gen2 execution environment.
customDomains
Additional domains to route to this service. Skiff2 will provision SSL certificates and load balancer routing for each domain listed here.
Type: string[]
There are two cases depending on whether the domain is in a managed zone:
Managed zones (allen.ai, apps.allenai.org) — Skiff2 can manage DNS for you, but you need to do both:
- Add the domain to
customDomainshere (SSL cert + LB routing) - Add the domain to
customDomainsin yourproject-config.json(DNS A record)
External/unmanaged domains (e.g. myapp.com) — Only add the domain here. DNS must be pointed at the GCP load balancer IP separately (outside of Skiff2).
Example (external domain):
{
"customDomains": ["myapp.com"]
}
Example (managed zone):
{
"customDomains": ["myapp.allen.ai"]
}
Once you set a custom domain in skiff2.json and deploy, Google will attempt to create an SSL certificate for it. If the domain's DNS is not pointed at the load balancer within an hour, Google's certificate creation will fail. This prevents the SSL certificate from ever being created and resolved.
To avoid this, update DNS at the same time you add the domain to customDomains and deploy. For external domains, point your DNS at the GCP load balancer IP first (or in tandem with the deploy); for managed zones, ensure the DNS A record (step 2 above) lands together with the SSL/LB change.
extraBuildArgs
Additional --build-arg flags to pass to docker build.
Type: string[]
Each item is passed as a separate argument to docker build. To pass a build arg, include both the flag and the value as separate entries:
Example:
{
"extraBuildArgs": ["--build-arg", "UI_IMAGE=gcr.io/${PROJECT_ID}/${REPO_NAME}-ui:${COMMIT_SHA}"]
}
The tokens ${PROJECT_ID}, ${REPO_NAME}, and ${COMMIT_SHA} are automatically substituted at build time.
dependsOn
Names of other services that must be built before this one. Controls build order.
Type: string[]
deploy
When false, this service is built but not deployed to Cloud Run.
Type: boolean
Default: true
machine
Cloud Run machine configuration for this service.
Type: object
| Field | Type | Default | Description |
|---|---|---|---|
minInstances | number | 0 | Minimum number of instances |
maxInstances | number | 10 | Maximum number of instances |
memory | string | "512Mi" | Memory allocation (e.g. "512Mi", "1Gi") |
cpu | number | 1 | Number of CPUs. Fractional values are allowed (e.g. 0.08, 0.5) — see below |
cpuIdle | boolean | true | Whether CPU is throttled when not handling requests |
Setting minInstances to 0 will result in cold starts for your application.
Fractional CPU values
cpu follows Cloud Run's CPU rules:
- Values below
1can be fractional, from a minimum of0.08up to (but not including)1, in increments of0.01(e.g.0.08,0.25,0.5). - Values of
1and above must be whole numbers supported by Cloud Run (1,2,4,6,8).
Cloud Run also enforces memory/CPU pairing constraints (e.g. more CPUs require more memory) — see the Cloud Run docs linked above for the current limits.
Example:
{
"machine": {
"minInstances": 1,
"maxInstances": 5,
"memory": "1Gi",
"cpu": 2,
"cpuIdle": false
}
}
requestTimeoutSeconds
The maximum time (in seconds) a request to this service is allowed to take before Cloud Run terminates it. See request timeout.
Type: number
Default: 300 (5 minutes)
The maximum allowed value is 3600 (60 minutes). Increase this for long-running requests such as large uploads or streaming responses.
Example:
{
"requestTimeoutSeconds": 600
}
maxConcurrentRequests
The maximum number of concurrent requests each instance of this service can receive before Cloud Run starts a new instance. See concurrency.
Type: number
Default: 80
The maximum allowed value is 1000. Lower this if each request is resource-intensive (so instances scale out sooner); raise it for lightweight, I/O-bound workloads to pack more requests onto each instance.
Example:
{
"maxConcurrentRequests": 120
}
startup
Type: ProbeConfig
Startup probe configuration for this service
| Field | Type | Default | Description |
|---|---|---|---|
| initialDelaySeconds | number | - | Number of seconds after the container has started before the probe is initiated. |
| timeoutSeconds | number | - | Number of seconds after which the probe times out. |
| periodSeconds | number | - | How often (in seconds) to perform the probe. |
| failureThreshold | number | - | Minimum consecutive failures for the probe to be considered failed after having succeeded. |
| path | string | / | Path to access on the HTTP server. |
| port | number | 8080 | Port number to access on the container. Must be in the range 1 to 65535. |
Example:
{
"startup": {
"initialDelaySeconds": 10,
"timeoutSeconds": 5,
"periodSeconds": 10,
"failureThreshold": 3,
"path": "/api/health",
"port": 8080
}
}
liveness
Type: ProbeConfig
Liveness probe configuration for this service
| Field | Type | Default | Description |
|---|---|---|---|
| initialDelaySeconds | number | - | Number of seconds after the container has started before the probe is initiated. |
| timeoutSeconds | number | - | Number of seconds after which the probe times out. |
| periodSeconds | number | - | How often (in seconds) to perform the probe. |
| failureThreshold | number | - | Minimum consecutive failures for the probe to be considered failed after having succeeded. |
| path | string | / | Path to access on the HTTP server. |
| port | number | 8080 | Port number to access on the container. Must be in the range 1 to 65535. |
Example:
{
"liveness": {
"initialDelaySeconds": 5,
"timeoutSeconds": 10,
"periodSeconds": 15,
"failureThreshold": 3,
"path": "/api/health",
"port": 8080
}
}
vpc
Type: Vpc
Vpc configuration for this service. Used to handle internal connections to things like databases or Redis.
| Field | Type | Default | Description |
|---|---|---|---|
| network | string | - | The VPC network that the Cloud Run resource will be able to send traffic to. At least one of network or subnetwork must be specified. If both network and subnetwork are specified, the given VPC subnetwork must belong to the given VPC network. If network is not specified, it will be looked up from the subnetwork. |
| subnetwork | string | - | The VPC subnetwork that the Cloud Run resource will get IPs from. At least one of network or subnetwork must be specified. If both network and subnetwork are specified, the given VPC subnetwork must belong to the given VPC network. If subnetwork is not specified, the subnetwork with the same name with the network will be used. |
| egress | `PRIVATE_RANGES_ONLY | ALL_TRAFFIC` | PRIVATE_RANGES_ONLY |
Example:
{
"vpc": {
"network": "skiff2-service-vpc",
"subnetwork": "skiff2-service-subnet"
}
}
sidecars
Type: List of Containers
Allows configuration of sidecars. These will run alongside the main container of this service and can be called by other containers in this service.
Configure a startup check on every sidecar. Cloud Run will route traffic to the main container before a sidecar is ready unless a startup probe gates it — without one, early requests that depend on the sidecar will return 502s.
All properties of a Container share a type with a Service.
| Field | Link |
|---|---|
| name | name |
| cwd | cwd |
| dockerFile | dockerFile |
| dependsOn | dependsOn |
| extraBuildArgs | extraBuildArgs |
| secretFiles | secretFiles |
| ephemeralStorage | ephemeralStorage |
| nfsVolumes | nfsVolumes |
| machine | machine |
| startup | startup |
| liveness | liveness |
| vpc | vpc |
includeDNSAuthorizationForExternalDomains
If true, also sets a DNS auth certificate for the custom domains set on this service. This is useful if you want to migrate an existing project without downtime caused by certificate authorization.
If used, the Skiff 2 infra deploy action will output a DNS record you will need to add to the DNS for your app's custom domain.
For example, for this DNS authorization record:
custom_domain_dns_authorizations = [
tolist([
{
"data" = "ad56a6az-5cae-40e5-a2eb-1bf63683583f.3.authorize.certificatemanager.goog."
"name" = "_acme-challenge.my-app.allenai.org."
"type" = "CNAME"
},
]),
]
You would add a CNAME record named _acme-challenge.my-app.allenai.org. with the data ad56a6az-5cae-40e5-a2eb-1bf63683583f.3.authorize.certificatemanager.goog..
RemoteServiceConfig fields
name (required)
A short identifier for this service. This must match the name of a service defined in the skiff2.json of a remote repository.
Type: string
customDomains
Additional domains to route to this service. Skiff2 will provision SSL certificates and load balancer routing for each domain listed here.
See the customDomains field in ServiceConfig for details on how routing works for managed vs. external domains.
Type: string[]
includeDNSAuthorizationForExternalDomains
See includeDNSAuthorizationForExternalDomains in the service config.