Skip to content

Secrets Management

Secrets Management for Kubernetes Workloads

Section titled “Secrets Management for Kubernetes Workloads”

This guide explains how to securely manage and expose secrets to your Runway Kubernetes workloads (GKE and EKS). Runway uses GitLab’s Vault installation to securely store secrets, which are then automatically made available as environment variables or mounted as files to your containers.

Runway uses a flexible approach to secrets management using Vault as the source of truth:

  1. Secrets are stored in Vault and referenced by your Runway configuration
  2. Multiple secret keys can be defined within a single Vault secret
  3. Secrets can be exposed to your container in two ways:
    • As environment variables (each key becomes an environment variable)
    • As mounted volumes (each key becomes a file; useful for file-based credentials)
  • Secret keys (which become environment variable names) should follow standard environment variable naming conventions (uppercase, letters/numbers/underscores only)
  • When mounting secrets as volumes, the mount path must be an absolute path (starting with /)

To manage secrets, you need access to the GitLab Vault installation. Follow these steps:

  1. Create an Access Request to provision an Okta group (Example)
  2. Create a Merge Request to set secret policy in Vault (Example)

When complete, service owners will only be granted access to secrets for their services.

Recommendation: Step 1 can take ~2 weeks for IT due to change management, so start the process as soon as you anticipate the need for secrets management.

  1. Determine your Runway service ID

    This is the ID defined in the Runway workload configuration file:

  2. Log into Vault

    If you do not have access to Vault, follow the instructions here. The path you need access to is runway/env/$environment/service/$runway_service_id.

  3. Go to your workload’s secrets path

    Navigate to runway/env/$environment/service/$runway_service_id where $environment is the environment (either staging or production) and $runway_service_id is your service ID. A service without any secrets will have a single empty secret called .placeholder.

  4. Create a secret with multiple keys

    Click on the Create secret button. At Path for this secret, enter a descriptive name for your secret collection (e.g., env-vars, database-credentials, or gcp-service-account).

    Under Secret data, add multiple key-value pairs:

    • Each key will become an environment variable name (when using secretEnvFrom) or a file name (when using secretVolumes)
    • For environment variables, keys should be in uppercase and contain only letters, numbers, and underscores
    • For file-based secrets, keys should be valid file names
    • Values should be the actual secret values you want to expose

    For example, for environment variables:

    API_KEY: "your-api-key-here"
    DATABASE_PASSWORD: "your-database-password"
    JWT_SECRET: "your-jwt-secret"

    For file-based secrets (e.g., GCP service account):

    credentials.json: '{"type": "service_account", "project_id": "..."}'
  5. Save the secret

    Click Save to store your secret in Vault.

Secrets added to Vault are available immediately as the External Secrets Operator has direct connectivity to Vault. You just need to configure secrets in your app.

Runway on Kubernetes deploys secrets separately from code deployments, unlike Runway on Cloud Run where they’re deployed together.

Since EKS clusters cannot directly access Vault, we use Terraform (provisioner) to synchronize secrets from Vault to AWS Secrets Manager. The provisioner automatically discovers and syncs all secrets under your workload path, so no additional configuration is needed — simply place your secrets in the correct path as outlined above.

Important considerations

  • Secret synchronization depends on the provisioner’s Terraform plan/apply cycle, which may introduce deployment delays.
  • For time-sensitive deployments, contact #f_runway to request manual provisioner pipeline execution.
  1. Log into Vault and go to the secret path for your environment/service
  2. Click on the existing secret you wish to change
  3. Click on Create new version in the top right
  4. Update the key-value pairs as needed
  5. Click Save

The new secret values will be synchronized to your Kubernetes cluster according to the deployment schedule.

  1. Log into Vault and go to the secret path for your environment/service
  2. Click on the ... next to the secret, and choose Permanently delete

Alternatively, you can remove individual keys from a secret by editing it and deleting the specific key-value pairs.

First, you need to list all secrets your application needs to access in the Runway configuration file (.runway/${RUNWAY_SERVICE_ID}/gke-service.yaml or .runway/${RUNWAY_SERVICE_ID}/eks-service.yaml):

.runway/example_service_id/gke-service.yaml
# Example Runway configuration
example_service_id:
workloadSecrets:
- name: env-vars
- name: database-credentials
targetName: k8s-secret-name
refreshInterval: 5m
- name: gcp-service-account

To make secrets accessible as environment variables, add the secret to the secretEnvFrom list in your configuration:

.runway/example_service_id/gke-service.yaml
# Example Runway configuration
example_service_id:
workloadSecrets:
- name: env-vars
- name: database-credentials
targetName: k8s-secret-name
refreshInterval: 5m
secretEnvFrom:
- env-vars
- k8s-secret-name

This will inject all keys from the referenced secrets as environment variables in your container.

Once deployed, all keys from the referenced secrets will be available as environment variables in your container. You can access them using your programming language’s standard method for reading environment variables.

To mount secrets as files in your container filesystem, use the secretVolumes configuration. This is particularly useful for file-based credentials like GCP service accounts, SSL certificates, or configuration files:

.runway/example_service_id/eks-service.yaml
# Example Runway configuration
example_service_id:
workloadSecrets:
- name: gcp-service-account
- name: ssl-certificates
# Mount secrets as files
secretVolumes:
- name: gcp-service-account
path: /secrets/gcp
- name: ssl-certificates
path: /etc/ssl/certs/custom
# Point Google APIs to the service account credentials
spec:
services:
app:
environment:
GOOGLE_APPLICATION_CREDENTIALS: '/secrets/gcp/credentials.json'

With this configuration:

  • The gcp-service-account secret will be mounted at /secrets/gcp
  • Each key in the secret becomes a file in that directory
  • For example, if the secret contains a key credentials.json, it will be available at /secrets/gcp/credentials.json
  • Files are mounted with read-only permissions (mode 0400) for security
  • The GOOGLE_APPLICATION_CREDENTIALS is added to the app service to configure Google APIs to use the credentials

You can use both environment variables and volume mounts for different secrets in the same application:

.runway/example_service_id/gke-service.yaml
# Example combining both methods
example_service_id:
workloadSecrets:
- name: env-vars
- name: database-credentials
- name: gcp-service-account
# Expose as environment variables
secretEnvFrom:
- env-vars
- database-credentials
# Mount as files
secretVolumes:
- name: gcp-service-account
path: /secrets/gcp
  1. Group related secrets: Use descriptive names for your secret collections and group related secrets together
  2. Choose the right method:
    • Use environment variables (secretEnvFrom) for simple key-value configurations
    • Use volume mounts (secretVolumes) for file-based credentials or complex configuration files
  3. Limit access: Only grant Vault access to team members who need to manage secrets
  4. Use secure values: Generate strong, random values for secrets
  5. Rotate regularly: Establish a routine schedule for rotating sensitive credentials
  6. Follow naming conventions for secret keys:
    • For environment variables: use uppercase with underscores (e.g., DATABASE_PASSWORD)
    • For volume mounts: use file names (e.g. credentials.json)