Skip to content

Migrating to Runway v2

Runway v2 generates your service’s Helm chart with Fairway and deploys it through GitLab CI components, replacing the hand-written v1 RunwayKubernetesService and RunwayKubernetesDeployment files. This guide walks through converting an existing GKE service.

All new and existing GKE services should move to v2. v1 is effectively deprecated, except for EKS services, which remain on v1 until v2 EKS support lands.

The provisioner side is untouched. Your workload inventory entry in config/runtimes/gke/workloads.yml stays as-is (runway_service_id, project_id, regions, members, groups), as does any managed infrastructure you provision.

The repository prerequisites are also unchanged:

  • infra-mgmt project access tokens
  • Your container image pipeline
  • The semantic-release job and the vX.Y.Z tag requirement
  • The CI/CD job-token allowlist for your deployment project
  • The “merge a feat/fix commit → tag → deploy” trigger flow
What v1 v2
Chart definition .runway/<id>/default-values.yamlkind: RunwayKubernetesService.runway/fairway.yamlkind: FairwayManifest
Deployment manifest .runway/<id>/gke-service.yamlkind: RunwayKubernetesDeployment.runway/deployment.yamlkind: RunwayManifestapiVersion: runway/v2
Runtime labels metadata.labels in gke-service.yaml common.labels in .runway/values.yaml
CI wiring include: of runwayctl helm-chart.yml release-platform component from common-ci-tasks (see Step 4)
.runway/
├── fairway.yaml # chart definition (was default-values.yaml)
├── deployment.yaml # Runway v2 manifest (was gke-service.yaml)
├── values.yaml # base Helm values, including labels
├── values-staging.yaml # staging overrides (optional)
└── values-production.yaml # production overrides (optional)

Values in values-staging.yaml and values-production.yaml override the base values.yaml for their environment. Empty override files may be omitted.

Use your existing default-values.yaml as the starting point. The conversion is not a straight rename: a few fields stay at the top level of spec, but most move under spec.values, and several need reshaping.

v1 RunwayKubernetesService Fairway manifest Note
spec.image (ends in :{{ .AppVersion }}) spec.image Remove the tag — Fairway rejects a tag and supplies Chart.AppVersion itself
spec.command spec.command unchanged
spec.container_port spec.containerPort camelCase
spec.protocol spec.protocol unchanged
spec.environment spec.values.environment moves under values
spec.scalability.* spec.values.scalability.* min_instancesminInstances, etc.
spec.resources.* spec.values.resources.* ephemeral-storageephemeralStorage
spec.observability.scrape_targets spec.metrics list of {port, path} — see gotchas
spec.startup_probe.path spec.startupProbe.path path stays at the top level
spec.startup_probe.{timeout_seconds,…} spec.values.startupProbe.* tuning moves under values
spec.liveness_probe / spec.readiness_probe same split path → spec.*Probe.path, tuning → spec.values.*Probe
spec.configMapVolumes spec.values.configMapVolumes
spec.initContainers spec.values.initContainers
deployment.ingress spec.values.ingress
deployment.httpRoute spec.values.httpRoute

A v1 default-values.yaml:

.runway/example-service/default-values.yaml (v1, before)
apiVersion: runway/v1
kind: RunwayKubernetesService
metadata:
name: example-service
spec:
image: "registry.gitlab.com/my-group/example-service:{{ .AppVersion }}"
command: ["/bin/frontend"]
container_port: 8080
scalability:
min_instances: 1
max_instances: 20
cpu_utilization: 70
observability:
scrape_targets:
- "localhost:8082"
resources:
requests:
cpu: "100m"
memory: "128Mi"
ephemeral-storage: "512Mi"
limits:
cpu: "200m"
memory: "256Mi"
ephemeral-storage: "1Gi"
startup_probe:
path: "/health?type=startup"
timeout_seconds: 1
period_seconds: 10
failure_threshold: 10
liveness_probe:
path: "/health?type=liveness"
timeout_seconds: 2
period_seconds: 10
failure_threshold: 3

becomes:

.runway/fairway.yaml (v2, after)
apiVersion: fairway/v1
kind: FairwayManifest
metadata:
name: example-service
spec:
image: "registry.gitlab.com/my-group/example-service"
command: ["/bin/frontend"]
containerPort: 8080
metrics:
- port: 8082
startupProbe:
path: "/health?type=startup"
livenessProbe:
path: "/health?type=liveness"
values:
scalability:
minInstances: 1
maxInstances: 20
cpuUtilization: 70
resources:
requests:
cpu: "100m"
memory: "128Mi"
ephemeralStorage: "512Mi"
limits:
cpu: "200m"
memory: "256Mi"
ephemeralStorage: "1Gi"
startupProbe:
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 10
livenessProbe:
timeoutSeconds: 2
periodSeconds: 10
failureThreshold: 3

Replace gke-service.yaml with a deployment.yaml. The v2 manifest drops serviceMixins entirely — runtime-specific resources (the Gateway, backend policies, and so on) are now injected by runwayctl kubernetes generate-flux --runtime gke. It adds a required chartRef pointing at the chart Fairway publishes for you.

.runway/deployment.yaml (v2)
apiVersion: runway/v2
kind: RunwayManifest
metadata:
name: example-service-gke
spec:
chartRef: "oci://registry.gitlab.com/my-group/example-service/chart/example-service:{% .ChartVersion %}"

The chart is published to your service project’s container registry under /chart/<fairway-manifest-name>, where <fairway-manifest-name> is the metadata.name from your Fairway manifest. Keep the literal {% .ChartVersion %} suffix — Runway fills it in at deploy time. (Note the {% … %} delimiter; this is not the {{ … }} Go-template syntax used by the v1 image tag.)

This is also where Runway-level configuration lives that used to be spread across the v1 service spec:

  • Load balancing moves from the v1 spec.load_balancing to spec.loadBalancing here (externalLoadBalancer, internalLoadBalancer, cloudflareSetting).
  • Provisioned infrastructure (managed Redis, Cloud SQL) is declared under spec.infrastructure — see Memorystore for Redis.
  • Vault access tokens and workload secrets are declared under spec.vaultAccessTokens and spec.workloadSecrets.

The mandatory infrastructure labels that lived under metadata.labels in gke-service.yaml move into a values.yaml file sitting next to the manifests, under common.labels. Keep them out of the Fairway manifest’s defaults so the generated chart stays free of GitLab-specific values.

.runway/values.yaml
common:
labels:
gitlab.com/department: eng-infra
gitlab.com/department_group: eng-infra-scalability
gitlab.com/owner_email_handle: fforster
gitlab.com/product_category: scalability

Use values-staging.yaml and values-production.yaml for per-environment overrides; their values override the base values.yaml. See the GitLab Infrastructure Standards for valid label values.

Step 4: Switch CI to the release-platform component

Section titled “Step 4: Switch CI to the release-platform component”

Replace the v1 runwayctl include with the release-platform component from common-ci-tasks. It bundles the Fairway chart-build component and the runwayctl deployment component, and ships its own pinned fairway_version and runway_version, so you no longer pin those yourself.

.gitlab-ci.yml (v1, before)
include:
- project: 'gitlab-com/gl-infra/platform/runway/runwayctl'
file: 'ci-tasks/service-project/helm-chart.yml'
ref: v3.82.0
inputs:
runway_service_id: example-service-gke
runway_version: v3.82.0
runtime: gke
.gitlab-ci.yml (v2, after)
include:
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/[email protected]
inputs:
runway_service_id: example-service-gke
runtime: gke

Useful optional inputs:

  • runway_manual_production_deploy: true — gate the production deployment behind a manual play; staging still deploys automatically.
  • release_platform_enabled: true plus canonical_project_id and canonical_registry_path — for security-mirror projects that sync artifacts to a canonical project. Leave these unset for ordinary services.

A few v1 capabilities do not carry over. Account for these before migrating:

v1 feature Status in v2
load_balancing.logging.* (GCP LB access-log tuning) Not configurable. generate-flux emits a GCPBackendPolicy that only sets the Cloudflare security policy; access logging falls back to GCP defaults.
observability ServiceMonitor interval / scheme tunables Not exposed. Set ports and per-endpoint path via spec.metrics in .runway/fairway.yaml, and enable scraping via metrics.prometheus.serviceMonitor.enabled in .runway/values.yaml (see Observability).
spec.strategy (RollingUpdate/Recreate, maxSurge, maxUnavailable) Not a chart concern; moving to the Runway manifest in a later release.

If your service depends on any of these, raise it in #g_runway before cutting over.

Open a merge request with the new .runway/ files and CI change. The MR pipeline runs the dry-run and validation jobs, which render the staging and production manifests so you can confirm they load and template cleanly without deploying.

On merge, the usual flow applies: a feat/fix commit triggers semantic-release, the new tag pipeline builds and publishes the Fairway chart, and the deployment pipeline is triggered. Staging deploys automatically; production deploys automatically too unless you set runway_manual_production_deploy: true.