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 (config/runtimes/gke/workloads.yml: runway_service_id, project_id, regions, members, groups) stays as-is, 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, the vX.Y.Z tag requirement, the CI/CD job-token allowlist for your deployment project, and the “merge a feat/fix commit → tag → deploy” trigger flow.

v1v2
.runway/<id>/default-values.yaml (RunwayKubernetesService).runway/fairway.yaml (FairwayManifest)
.runway/<id>/gke-service.yaml (RunwayKubernetesDeployment).runway/deployment.yaml (RunwayManifest, runway/v2)
runtime labels in gke-service.yaml metadata.labels.runway/values.yaml under common.labels
include: of runwayctl helm-chart.ymlrelease-platform component from common-ci-tasks
.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 RunwayKubernetesServiceFairway manifestNote
spec.image (ends in :{{ .AppVersion }})spec.imageRemove the tag — Fairway rejects a tag and supplies Chart.AppVersion itself
spec.commandspec.commandunchanged
spec.container_portspec.containerPortcamelCase
spec.protocolspec.protocolunchanged
spec.environmentspec.values.environmentmoves under values
spec.scalability.*spec.values.scalability.*min_instancesminInstances, etc.
spec.resources.*spec.values.resources.*ephemeral-storageephemeralStorage
spec.observability.scrape_targetsspec.metricPortsport numbers only — see gotchas
spec.startup_probe.pathspec.startupProbe.pathpath stays at the top level
spec.startup_probe.{timeout_seconds,…}spec.values.startupProbe.*tuning moves under values
spec.liveness_probe / spec.readiness_probesame splitpath → spec.*Probe.path, tuning → spec.values.*Probe
spec.configMapVolumesspec.values.configMapVolumes
spec.initContainersspec.values.initContainers
deployment.ingressspec.values.ingress
deployment.httpRoutespec.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
metricPorts: [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/release-platform@v4.19
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 featureStatus 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 tunables (path, interval, scheme, serviceMonitor.enabled)Superseded by metricPorts, which takes port numbers only.
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.