Skip to content

Getting Started

This guide will take you through the process of deploying an app via Runway.

Follow these steps to deploy an initial “Hello World” style app with Runway. This will ensure that any changes that follow are applied on a stable foundation (i.e., a successful initial deployment).

A service repository is where the source code for your app lives, where your Docker images are created/pushed, and usually (see note below) where the Runway configuration files are defined.

If you do not yet have a service repository created for your app, go ahead and create one.

Let’s create a simple one-line Dockerfile in your service repository to build an image:

Dockerfile
FROM registry.gitlab.com/gitlab-com/gl-infra/ci-images/nginx-runway:latest

Now that we have a Dockerfile in place, we can create/update your service repository’s .gitlab-ci.yml to build/push images:

.gitlab-ci.yml
stages:
- build
include:
- project: 'gitlab-com/gl-infra/common-ci-tasks'
ref: v2.73.0
file: 'docker.yml'
build:
stage: build
variables:
DOCKER_DESTINATION: ${CI_REGISTRY_IMAGE}/app:${CI_COMMIT_SHORT_SHA} # replace "app" with another name, if you wish
# DOCKER_BUILD_FILE: Dockerfile.nginx # defaults to Dockerfile
extends:
- .docker_buildx_base
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

Go ahead and merge this change. You should see a container image called app get built and pushed to your service repository’s container registry, which we can deploy using Runway.

In your service repository, create a .runway/runway.yml file that looks like this:

.runway/runway.yml
apiVersion: runway/v1
kind: RunwayService
metadata:
name: <VALUE> # your hyphenated service name
department: <VALUE> # https://handbook.gitlab.com/handbook/company/infrastructure-standards/labels-tags/#gitlab-department-gl_dept
department_group: <VALUE> # https://handbook.gitlab.com/handbook/company/infrastructure-standards/labels-tags/#gitlab-department-group-gl_dept_group
owner_email_handle: <VALUE> # https://handbook.gitlab.com/handbook/company/infrastructure-standards/labels-tags/#owner-email-handle-gl_owner_email_handle
product_category: <VALUE> # https://handbook.gitlab.com/handbook/company/infrastructure-standards/labels-tags/#gitlab-product-category-gl_product_category
spec:
container_port: 80

The metadata fields above are required. You will need to replace <VALUE> using the links that follow as a reference.

You will also need to create two more files, which can be empty for now:

.runway/env-staging.yml
---
.runway/env-production.yml
---

Commit/push the changes to the .runway/* files and move onto the next step.

File an MR to add a new workload to the config/runtimes/cloud-run/workloads.yml file in the provisioner project:

workloads.yml
- name: your-workload # must be unique
project_id: 123456789 # your service repository GitLab Project ID
members:
- username # GitLab username that will trigger deployments
exclude_name_suffix: true # omit adding random suffix to workload name

As we did not specify a region, it will be deployed to us-east1 by default.

For bootstrapping purposes, you should not specify any other options. However, once you are ready to get your own app deployed, refer to the schema for all the possible options.

Once your provisioner MR is merged and deployed, you can continue to the next step.

The following steps will allow your deployment project access to your service repository for the purposes of downloading artifacts (see docs). These artifacts are downloaded during the deployment process and contain your Runway configuration files:

  1. Open Settings > CI/CD on your service project

  2. Open the Job token permissions drop-down

  3. In the CI/CD job token allowlist section, click on Add then select Group or project

  4. Enter your deployment project:

    gitlab-com/gl-infra/platform/runway/deployments/<workload name>

We now need to update your service repository’s .gitlab-ci.yml to trigger staging and production deployments via Runway:

stages:
- build
- validate
- runway_staging
- runway_production
include:
- project: 'gitlab-com/gl-infra/common-ci-tasks'
ref: v2.73.0
file: 'docker.yml'
- project: 'gitlab-com/gl-infra/platform/runway/runwayctl'
file: 'ci-tasks/service-project/runway.yml'
inputs:
runway_service_id: your-workload
image: "$CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHORT_SHA" # Replace "app" if you used a different name
runway_version: v3.61.13 # Replace with latest version: https://gitlab.com/gitlab-com/gl-infra/platform/runway/runwayctl/-/releases
build:
stage: build
variables:
DOCKER_DESTINATION: ${CI_REGISTRY_IMAGE}/app:${CI_COMMIT_SHORT_SHA}
# DOCKER_BUILD_FILE: Dockerfile.nginx # defaults to Dockerfile
extends:
- .docker_buildx_base
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

We are ready to access your workload’s endpoint and check if it’s working!

The best place to find the links to access your environments (once they have been deployed) is through OperateEnvironments in your service project. In there you should see two environments, staging and production. Clicking on the Open button will take you to the URL to access that environment:

Environments

If you were able to access your staging and production endpoints, then you have successfully bootstrapped your workload!

You can now make changes to deploy your own app.

Now that you have a working Runway workload, you can go ahead and make changes to get your app deployed.

Customize your Dockerfile to build your application according to your specific needs. Feel free to adjust the build process as needed for your project requirements.

The contents of the Runway config file depends on the type of workload you are deploying:

  • RunwayService: this type of workload deploys a Cloud Run Service. You can configure the service with an external and/or internal load balancer.

    Example: the service deployed during bootstrapping is a RunwayService that runs nginx and serves a Hello World style page.

  • RunwayJob: this type of workload deploys a Cloud Run Job, which can be configured to trigger on a schedule or on-demand via CI.

    Example: woodhouse-handover, which creates an EOC handover issue at set times of the day.

Reconciler checks for config files in this order:

  1. First looks in .runway/<runway service id>/ directory
  2. If that path doesn’t exist, it falls back to checking in the .runway/ directory

This creates a hierarchy where service-specific configurations (in the service ID folder) take precedence over general configurations in the main runway folder.

Example tree structure for running a single deployment:

  • Directory.runway
    • runway.yml
    • env-staging.yml
    • env-production.yml
  • .gitlab-ci.yml
  • etc

Example tree structure for running multiple deployments:

  • Directory.runway
    • runway.yml
    • env-staging.yml
    • env-production.yml
    • Directoryapp-backend
      • runway.yml
      • env-staging.yml
      • env-production.yml
  • .gitlab-ci.yml
  • etc

In the example above, when running a deployment for Runway service ID app-backend, it will use the config files under the app-backend path, however deployments for any other Runway service ID will use the config files under .runway/.

Alternatively, to be explicit, you could also make it look like the following and it will have the same effect with the exception that deployments for any other Runway service ID will fail because there is no runway.yml (nor env-*.yml) file in the .runway/ directory:

  • Directory.runway
    • Directoryapp-frontend
      • runway.yml
      • env-staging.yml
      • env-production.yml
    • Directoryapp-backend
      • runway.yml
      • env-staging.yml
      • env-production.yml
  • .gitlab-ci.yml
  • etc

Once you have your config files in the right places, scroll down to read the section on updating your project’s .gitlab-ci.yml to trigger multiple deployments.

If you only have a single runway.yml file then it will be used for all stages (i.e., staging, production). You can, alternatively, create different Runway config files for each stage by defining runway-<stage>.yml.

Example tree structure might look like this:

  • Directory.runway
    • Directoryapp-frontend
      • runway.yml
      • runway-staging.yml
      • env-staging.yml
      • env-production.yml
    • Directoryapp-backend
      • runway-production.yml
      • runway-staging.yml
      • env-staging.yml
      • env-production.yml
  • .gitlab-ci.yml
  • etc
  • In the app-frontend service above, the production deployment would use runway.yml (as there is no runway-production.yml) while staging would use runway-staging.yml.

  • In the app-backend service above, we are being explicit by defining a config file for each stage (runway-staging.yml and runway-production.yml). Runway currently supports two stages (staging and production). It is your choice whether to be explicit or not, however we do recommend being explicit purely from a future-proof perspective so that if Runway ever supports more stages, they will not accidentally use your runway.yml file.

Runway seamlessly supports running multiple workloads from a single service repository. This flexibility is perfect for scenarios where you have a unified codebase—like a full-stack application with both frontend and backend components sharing the same Docker image—but need to deploy them as separate, independently-managed workloads in your environment.

To achieve this, you will need to add as many entries as deployments you need all referencing the same project ID. Example:

workloads.yml
- name: app-frontend
project_id: 123456789
members:
- gitlab-username
- name: app-backend
project_id: 123456789
members:
- gitlab-username

Runway config and deployments in separate repo

Section titled “Runway config and deployments in separate repo”

There are two ways the service project could be set up:

Runway config in service repository

Using this setup, every commit to the default branch of the service repository will trigger a Runway deployment pipeline in the deployment project. The image can be configured with the CI_COMMIT_SHORT_SHA since the pipeline is triggered from the service project where the application Docker image is built.

Example: https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist

Runway config in separate repository

Using this setup, the image input variable has to be updated to deploy the new version of the application. This grants the user a higher degree of control over each deployment.

Example: https://gitlab.com/gitlab-com/gl-infra/cells/topology-service-deployer

See environment variables and secrets management.

By default, your Runway service will be deployed with a public-facing load balancer accessible via:

  • Production: <service-id>.runway.gitlab.net
  • Staging: <service-id>.staging.runway.gitlab.net

You can restrict access to your service by deploying it with an internal load balancer instead of an external one. This ensures only GitLab Rails can communicate with your service. Once deployed, your service will be accessible through the following endpoints:

  • Production: <service-id>.internal.runway.gitlab.net
  • Staging: <service-id>.internal.staging.runway.gitlab.net

This is achieved by modifying your service’s runway.yml as per the following:

runway.yml
apiVersion: runway/v1
kind: RunwayService
metadata:
...
spec:
...
load_balancing:
external_load_balancer:
enabled: false # defaults to true
internal_load_balancer:
enabled: true # defaults to false

Renovate is a tool that checks for new dependency versions and, if found, will periodically create MRs to upgrade them to the newest version. It is used extensively within “Infrastructure”.

The easiest way to keep up with Runway releases is to enable Renovate CI triggered merge requests on your service project by doing the following:

  1. Add @glrenovatebot as a Developer on your service repository under Manage > Members.
  2. Add runway-workloads as a topic in your project under Settings > General > Naming, description, topics.
  3. Set RENOVATE_DISABLED to true as a CI/CD variable if you are currently getting MRs filed by renovate-bot.yml from common-ci-tasks

If you’re not already using Renovate, once you’ve added the runway-workloads topic to your service project, Renovate CI will be able to discover your project and onboard it by opening a merge request like this one.

If you have an existing renovate.json file, Renovate will not update it for you so you will need to update the file yourself and add Runway’s Renovate config preset to the list of base configs (i.e. the extends keyword). For example:

renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"gitlab>gitlab-com/gl-infra/common-ci-tasks:renovate-common",
"gitlab>gitlab-com/gl-infra/platform/runway/runwayctl:renovate-runway"
]
}

This will look for patterns like runway_version: v2.40.0 in your .gitlab-ci.yml file and keep the version up to date.

At the moment the designed way to roll back a deployment is roll back a commit on the main branch, which will trigger a new deployment to Runway with the reverted code.

By default, metrics are reported for all Runway services. To view metrics, you can use service filter on Runway Service Metrics dashboard.

Recommendation: For additional features, such as custom service overview dashboard, alerts, and capacity planning, refer to the observability page.

By default, application container logs can be viewed in Cloud Logging UI by filtering resource.labels.service_name to your runway_service_id.

To learn more, refer to the observability page.

When a pipeline is triggered with insufficient permissions, failed job includes following error message:

Terraform has been successfully initialized!
Acquiring state lock. This may take a few moments...
│ Error: Error acquiring the state lock
│ Error message: HTTP remote state endpoint invalid auth
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.
Error: failed to execute terraform command: exit status 1

To fix, there are two potential options:

  1. Job can be retried by member with the required permissions, or
  2. Add your GitLab username to members attribute in config/runtimes/cloud-run/workloads.yml and retry the job yourself (example MR).
  • If your deployment fails for any reason, check the deployment logs.
  • If the downstream pipeline failed to be executed, there’s probably a permission mismatch.

Currently, a person triggering a deployment must have “Developer” permissions in the runwayctl project and “Maintainer” permissions in the generated deployment project (example). To grant additional permissions, update the config/runtimes/cloud-run/workloads.yml file in the provisioner repo from step 1.