Skip to content

Getting Started

This guide walks you through onboarding a service to Runway for GitLab Dedicated. On Dedicated, your service is a modular feature (or module): a Helm chart that is deployed into the Kubernetes cluster of every Dedicated tenant that enables it, together with the AWS infrastructure it needs.

Architecture diagram in three columns. Dedicated: the Tenant Model, which holds modular_features, and Dedicated inputs such as networking and authentication secrets feed Instrumentor, which runs each action with the right permissions. Runway: Instrumentor runs the provision action (Terraform resources, AWS IAM permissions) and the deploy action (Helm chart and image, Kubernetes permissions); both read feature versions from the Module Catalog. Tenant: the provision action creates the tenant infrastructure in the AWS account, and the deploy action creates the tenant workload in the Kubernetes cluster.

Unlike the GKE and EKS runtimes, Runway does not deploy your service from your project’s pipeline. Releasing and deploying are separate steps:

  1. Release. When you tag a release, your project’s pipeline builds and publishes the Helm chart with Fairway, and creates an entry for the release in the Module Catalog. The entry records the Helm chart, its default values, and the infrastructure the module needs.
  2. Provision and deploy. For each tenant, the Tenant Model decides whether the module is enabled, which version runs, and the tenant-specific settings. Dedicated’s Instrumentor runs two Runway actions, each with only the permissions it needs:
    • the provision action (runwayctl dedicated provision) creates the AWS infrastructure in the tenant’s AWS account, such as S3 buckets and RDS instances;
    • the deploy action (runwayctl dedicated deploy) deploys the module version from the Module Catalog into the tenant’s Kubernetes cluster.

Your service repository needs the same prerequisites as for the Kubernetes runtimes. See Kubernetes: Before you begin for details:

  1. Container image pipeline: Your repository builds and pushes container images to the GitLab Container Registry.
  2. Semantic versioning: Include the semantic-release job. Every release creates a new module version in the Module Catalog. Your repository must have at least one tag matching vX.Y.Z.

Step 1: Add your service to the workload inventory

Section titled “Step 1: Add your service to the workload inventory”

The workload inventory registers your service with Runway. Runway then creates the deployment project that publishes your releases to the Module Catalog.

  1. Choose a Runway service ID: Tenants use this ID as the module name, so choose it with care. It must be 1 to 53 lowercase letters, digits, and hyphens, and must start and end with a letter or digit. It’s also used as the Kubernetes namespace on the tenant’s cluster. If you also deploy to GKE or EKS, use a separate service ID for each runtime, for example example-service-dedicated.

  2. Add your service to config/runtimes/dedicated/workloads.yml in the provisioner:

    - runway_service_id: example-service-dedicated
    project_id: 12345678
    groups:
    - gitlab-org/your-group

    Dedicated workloads have no regions: the Tenant Model decides where a module runs.

  3. Create a merge request and assign it to a Runway team member (see CODEOWNERS).

The deployment project downloads the catalog entry from your project’s pipeline.

  1. Navigate to your project’s Settings → CI/CD → Job token permissions.
  2. In the CI/CD job token allowlist section, add gitlab-com/gl-infra/platform/runway/deployments/<runway-service-id>.
  3. Set Permission configuration to Default permissions.

Dedicated uses the Fairway manifest and the values files of the Runway v2 layout, with a runway/v3 module manifest, module.yaml, in place of the deployment*.yaml Runway manifest. If your service already runs on GKE with Runway v2, add the Dedicated files to the existing directory: values.yaml is shared, while module.yaml and values-dedicated.yaml only apply to Dedicated.

.runway/
├── fairway.yaml # chart definition, including infrastructure dependencies
├── module.yaml # Runway v3 manifest for the Dedicated service ID
├── values.yaml # Helm values shared by all runtimes
└── values-dedicated.yaml # Helm values for Dedicated (optional)

fairway.yaml defines your service’s Helm chart. The v2 migration guide and the Fairway service owner guide describe it in detail.

Declare the infrastructure your module needs under spec.infrastructure. Each dependency has a presence:

Presence Meaning
REQUIRED The module doesn’t work without it. The module manifest must enable a default for it.
OPTIONAL The module works with or without it. Tenants may configure it.
UNUSED The module doesn’t use it (the default). Tenants must not configure it.
.runway/fairway.yaml
apiVersion: fairway/v1
kind: FairwayManifest
metadata:
name: example-frontend
spec:
image: "registry.gitlab.com/gitlab-com/gl-infra/platform/runway/example-service"
command: ["/bin/frontend"]
containerPort: 8080
infrastructure:
postgresql:
presence: OPTIONAL
object_store:
presence: REQUIRED

The presence is copied into the Module Catalog entry. When a tenant deploys the module, Runway checks the tenant’s buckets and instances against it. The S3, RDS, and ElastiCache pages describe each dependency.

module.yaml is the Runway manifest of your module on Dedicated, and is required. Its metadata.name is your Dedicated service ID: any file named module.yaml or module-<suffix>.yaml works, and Runway picks the one whose metadata.name is the Runway service ID.

It sets the default configuration of the buckets and instances your module uses, keyed by a logical name. Tenants override these defaults in the Tenant Model. A REQUIRED dependency needs an enabled default, and an UNUSED one must have none. If your module talks to another module on the tenant, declare the peer under spec.connections, and Runway delivers the credentials for that connection to your pods. If your module needs a key that no other system owns, such as an encryption key, declare it under spec.secrets, and Runway generates it and delivers it to your pods. See the Module Manifest reference, Connections, and Secrets.

.runway/module.yaml
apiVersion: runway/v3
kind: RunwayManifest
metadata:
name: example-service-dedicated
spec:
infrastructure:
s3:
uploads:
defaultConfig: {}

The chart is published to your project’s container registry under /chart/<fairway-manifest-name>, and the Module Catalog entry records it from there, so the module manifest doesn’t name it.

Runway merges the default Helm values of each release in this order. Later layers override earlier ones: objects are merged, lists are replaced.

  1. Fairway’s built-in defaults.
  2. spec.values from fairway.yaml.
  3. values.yaml.
  4. values-dedicated.yaml.
  5. Any files listed in the module manifest’s spec.extraValueFiles. File names can use {{ .Runtime }}, which is dedicated.

The result is stored in the Module Catalog entry. Tenants can override any of it with values_override.helm.values in the Tenant Model. Helm Values describes the files, the merge, and the templates for tenant-specific values such as the GitLab instance’s domain.

How you set up CI depends on where the Helm chart is deployed.

Include the release-platform component from common-ci-tasks with runtime: dedicated. It builds and publishes the Fairway chart, creates the catalog entry, and triggers the deployment project.

.gitlab-ci.yml
include:
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/[email protected]
inputs:
runway_service_id: example-service-dedicated
runtime: dedicated

If your Runway directory is not .runway, set runway_dir, for example runway_dir: .runway/frontend.

A modular feature often runs on GitLab.com (GKE) and is published for Dedicated, using the same Helm chart. release-platform builds the chart and deploys it to one runtime, so include it for GKE, and add runwayctl’s kubernetes-v2-service component for Dedicated. Both use the chart that release-platform builds.

.runway/
├── fairway.yaml # one chart for both runtimes
├── deployment-gke.yaml # GKE, metadata.name: example-service-gke
├── module.yaml # Dedicated, metadata.name: example-service-dedicated
├── values.yaml # shared by both runtimes
├── values-staging.yaml # GKE staging (optional)
├── values-production.yaml # GKE production (optional)
└── values-dedicated.yaml # Dedicated (optional)
.gitlab-ci.yml
include:
# Builds and publishes the Helm chart, and deploys it to GKE.
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/[email protected]
inputs:
runway_service_id: example-service-gke
runtime: gke
runway_version: v4.100.0
# Publishes the same Helm chart to the Module Catalog for Dedicated.
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/platform/runway/runwayctl/[email protected]
inputs:
runway_service_id: example-service-dedicated
runtime: dedicated
runway_version: v4.100.0
runway_dir: .runway
fairway_manifest: .runway/fairway.yaml
stage: deploy

Each service ID needs its own workload inventory entry, in config/runtimes/gke/workloads.yml and config/runtimes/dedicated/workloads.yml respectively, and its own deployment project in the job token allowlist.

Open a merge request with the new .runway/ files and the CI change. The merge request pipeline runs in dry-run mode: it creates the catalog entry and triggers the deployment project, but doesn’t publish anything. Use it to check that your manifests and values are valid.

After the merge request is merged, a feat: or fix: commit triggers semantic-release. The tag pipeline:

  1. publishes the Helm chart,
  2. creates the catalog entry (runwayctl catalog create), and
  3. triggers the deployment project, which adds the entry to the Module Catalog as data/<runway-service-id>/<version>.yaml.

Catalog entries are never changed or deleted: every release adds a new version.

A module runs on a tenant once the tenant’s Tenant Model enables it:

{
"modular_features": {
"modules": {
"example-service-dedicated": {
"enabled": true,
"version": "v1.25.0"
}
}
}
}

The Tenant Model reference lists every option, including the infrastructure and Helm value overrides.