Configuration¶
version: 1 # schema version (required, only 1)
app: hello-api # lowercase letters, digits, hyphens; up to 40 chars
vars: # optional user variables: ${vars.NAME}
data_project: my-data-project
provider:
project: my-gcp-project # project ID (required)
region: europe-west1 # Cloud Run and Cloud Build region (required)
enable_apis: true # enable missing APIs (default false)
apis: [telemetry.googleapis.com] # extra APIs runway cannot infer
create_build_resources: true # create repo, source bucket, build SA + roles (default false)
impersonate_service_account: deployer@my-gcp-project.iam.gserviceaccount.com # optional; the CLI flag wins
tags: # optional tags bound to the PROJECT before anything is created,
"123456789012/allowIngressAllForCloudRun": allow-ingress-all # then awaited until effective
# The three names below are required for source builds, unless
# create_build_resources is true: then they default to `runway`,
# `${project}-runway-sources` and `runway-build@${project}.iam.gserviceaccount.com`.
artifact_repository: applications # source builds
artifact_location: europe-west1 # optional, defaults to region
source_bucket: "${project}-runway-sources" # source builds, name without gs://
build_service_account: "builds@${project}.iam.gserviceaccount.com" # source builds
buckets: # created and kept configured by runway: ${buckets.KEY}
# An existing bucket is only updated if runway created it for this app
# (labels managed-by=runway, runway-app=<app>); any other existing bucket
# is used as is. Add those labels to let runway manage a bucket you created.
cache:
name: "${project}-hello-cache" # globally unique
location: europe-west1 # default: provider.region
storage_class: STANDARD # optional
versioning: false # optional
delete_after_days: 30 # optional lifecycle rule
labels: { team: web } # optional (runway adds managed-by/runway-app)
secrets: # created by runway WITHOUT a value: ${secrets.KEY}
api-key:
name: "${app}-${stage}-api-key" # secret ID, default: the key
adders: [group:devops@example.com] # get roles/secretmanager.secretVersionAdder
# locations: [europe-west1] # default [provider.region]; [] = automatic replication
# labels: { team: web }
service:
source: . # build context, relative to runway.yaml
dockerfile: Dockerfile # relative to source; default: `Dockerfile` if present, else buildpacks
# builder: gcr.io/buildpacks/builder:latest # force buildpacks with this builder
rebuild: on-change # default: rebuild when the source or a base image changes; `always` = every deploy
# image: europe-west1-docker.pkg.dev/my-gcp-project/applications/hello:1.2.3 # instead of source
port: 8080 # default 8080; injected as $PORT
cpu: "1" # 1, 2, 4, 6, 8 or 0.08-1 (also "500m"); default 1
memory: 512Mi # 128Mi-32Gi; default 512Mi
timeout_seconds: 60 # 1-3600; default 300
concurrency: 80 # 1-1000; default 80
min_instances: 0 # default 0
max_instances: 10 # default 10
public: false # default false (private)
ingress: all # all (default) | internal | internal-and-cloud-load-balancing
bootstrap: {} # first deploy only: restricted ingress until `tags` are effective
# ingress: internal # default: ingress used until the tags are effective
# image: us-docker.pkg.dev/cloudrun/container/hello # optional placeholder instead of the real app
otel_collector: # optional Google-built OpenTelemetry Collector sidecar
# version: "0.160.0" # default: the newest release, looked up at plan/deploy time
# cpu: "1" # default 1
# memory: 512Mi # default 512Mi
# config: | # default: OTLP on localhost:4317/4318 -> Cloud Trace,
# ... # Cloud Logging, Managed Service for Prometheus
sidecars: # extra containers next to the app (container name: settings)
sql-proxy:
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.0
args: ["--port=5432", "my-project:europe-west1:db"]
cpu: "0.5" # default 1
memory: 256Mi # default 512Mi
# command: [...] # replaces the image entrypoint
# env: { LOG_LEVEL: info }
# secrets: { TOKEN: { secret: proxy-token } } # env vars only; accessor granted
health_check: { port: 5432 } # TCP check; add `path: /ready` for HTTP
# start_before_app: true # default: the app starts once this check passes
# volumes: { cache: /cache } # mount service volumes (name: path)
health_check: # optional HTTP probes (default: Cloud Run's TCP startup probe)
path: /healthz
startup: # defaults: every 10s, timeout 3s, 12 failures, no delay (values up to 240s)
period_seconds: 10
liveness: true # default true (every 30s, timeout 3s, 3 failures); false or a map like startup
service_account: runtime@my-gcp-project.iam.gserviceaccount.com # required
env:
LOG_LEVEL: info
secrets: # the runtime account gets secretAccessor on each one
DATABASE_URL:
secret: database-url # or projects/<project>/secrets/<id>
version: "1" # a number, or "latest"; omitted: newest version pinned at deploy
API_KEY:
secret: "${secrets.api-key}" # a secret runway creates (see `secrets:` above)
tls: # mounted as a file instead of an environment variable
secret: tls-cert
path: /secrets/tls/cert.pem # one secret per directory; default version: latest (read live)
identity: # optional: manage the runtime service account
create: true # create service_account if missing
display_name: hello runtime
roles: # granted to service_account; exactly one target each
- role: roles/bigquery.dataViewer
dataset: my-data-project.my_dataset # or PROJECT:DATASET
- role: roles/bigquery.jobUser
project: my-data-project
- role: roles/storage.objectUser
bucket: "${buckets.cache}"
- role: roles/secretmanager.secretAccessor
secret: database-url # or projects/<project>/secrets/<id>
tags: # Resource Manager tags bound to the service
"123456789012/allow-public-access": "true" # ORG_OR_PROJECT_ID/key: value
volumes: # Cloud Storage mounts (the runtime account gets
cache: # objectViewer if read_only, else objectUser, on the bucket)
bucket: "${buckets.cache}" # or any bucket name
mount_path: /mnt/cache
read_only: false # default false
mount_options: [implicit-dirs]
iap: # Identity-Aware Proxy
enabled: true # default true when the block is present
members: [group:finops@example.com] # get roles/iap.httpsResourceAccessor
retry: # per-step retries for deploy (top level only)
attempts: 3 # total attempts per step, 1-20; default 3
delay: 5s # initial delay, doubles each attempt; default 5s
max_delay: 60s # cap; default 60s
stages: # at least one; --stage must name one of these
dev:
service:
max_instances: 2
prod:
provider:
project: my-prod-project
service:
min_instances: 1
env:
LOG_LEVEL: warning
Sidecars¶
service.sidecars adds containers that run next to the application in every
instance (Cloud Run allows 10 containers in total, including the app and the
OpenTelemetry Collector). Typical uses: a database proxy, a local cache, an
authentication or TLS proxy, a log forwarder.
- Containers share the network: the app reaches a sidecar on
localhost. Only the app receives requests; a sidecar'shealth_check.portmust differ fromservice.port. - With
start_before_app(the default), the app container is namedappand Cloud Run starts it after the sidecar's startup check passes. Without ahealth_check, the sidecar counts as started as soon as it runs. secretsare exposed as environment variables (pinned to the newest version at deploy time whenversionis omitted) and the runtime service account is grantedroles/secretmanager.secretAccessoron them.volumesmounts volumes declared underservice.volumes(the app mounts them too, at theirmount_path).- In a stage,
sidecars: { NAME: null }removes an inherited sidecar; a map replaces it entirely. - Plans show each sidecar on one line (
sidecars.NAME) with its image, CPU, memory and a fingerprint of the rest of its settings.
CPU is allocated per container: with request-based billing, sidecars only get CPU while requests are being served.
Stage override precedence¶
From highest to lowest:
- Command-line overrides:
--imageonplan/deploy. stages.<stage>.provider/stages.<stage>.service.- Top-level
provider/service. - Built-in defaults (listed above).
Scalars are replaced. env, secrets, tags, volumes and vars are
merged key by key; a stage can remove an inherited key by setting it to null. The
identity and iap blocks are replaced as a whole by a stage that sets them. The deployment mode is chosen
by the highest layer that sets image or source/dockerfile: a stage that
sets image replaces a top-level source build (and vice versa). Setting both
in the same block is an error.
Variables¶
${...} is replaced in provider.source_bucket,
provider.build_service_account, bucket names, service.service_account,
env values, secret names, identity (targets and display name), volume
buckets and IAP members. Available: ${project}, ${region}, ${app},
${stage}, ${vars.NAME} (top-level vars, overridden by
stages.<stage>.vars; variables may use the built-ins) and ${buckets.KEY}
(the resolved name of a declared bucket). $$ is a literal $. Unknown
variables are validation errors.
Validation¶
runway validate checks every stage (or --stage) without credentials:
unknown fields (with line/column and the list of valid fields), duplicate
keys, types, value ranges, CPU/memory combinations, name formats (project,
region, repository, bucket, service accounts, secrets, env names), reserved
variables (PORT, K_SERVICE, K_REVISION, K_CONFIGURATION), env/secret
name clashes, image references, min_instances <= max_instances, the
49-character service-name limit, the presence of the build context and
Dockerfile, and the provider fields required for source builds. It warns
about mutable image tags, latest secret versions, credential-looking plain
env vars and billed warm instances.
Secret values never appear in runway.yaml, in runway's output or in the
resources it writes: only secret@version references are sent to Cloud Run.