Skip to content

S3 Object Storage

Runway can provision a managed S3 bucket for your EKS workload and grant the workload’s pod identity (IRSA) object access to it. Runway owns the lifecycle of the bucket: it creates one bucket per environment in the Runway AWS accounts, attaches a bucket-scoped IAM policy to your workload’s role, and publishes the bucket name and region to both Vault and AWS Secrets Manager.

This guide assumes you already have a working Runway for Kubernetes deployment. If you do not, start with Getting Started first.

Unlike GCS on GKE, S3 on EKS is fully wired at deploy time: declare the bucket in your Fairway manifest and Runway’s EKS deployment tooling handles secret projection and mounting automatically — no manual Vault reads, no hand-written secretRef.

Provisioning a bucket and wiring it into your deployment touches four files across two repositories:

  1. Define the bucket in config/runtimes/eks/s3.yml (provisioner) — name, versioning, lifecycle, and labels.
  2. Grant your workload access in config/runtimes/eks/workloads.yml (provisioner) — references the bucket by its name via an s3_buckets list.
  3. Declare the bucket in your Runway deployment manifest (deployment-eks.yaml) — references the same name via spec.infrastructure.s3_object_store.
  4. Declare the dependency in your Fairway manifest (fairway.yaml) — spec.infrastructure.object_store.

A bucket belongs to a single service: each bucket may be referenced by at most one workload. One bucket is provisioned per environment (staging and production), each in its own AWS account. The physical bucket is named runway-<name>-<environment> — the runway- prefix and -<environment> suffix are appended in Terraform so the bucket name is globally unique (S3 bucket names are unique across every AWS account, not just GitLab’s own).

File an MR to add your bucket to config/runtimes/eks/s3.yml in the Runway provisioner:

config/runtimes/eks/s3.yml
- name: example-bucket
versioning: true
abort_incomplete_multipart_upload_days: 7
lifecycle_rules:
- id: expire-old-versions
noncurrent_version_expiration:
days: 90
- id: downgrade-storage-class
transition:
- days: 30
storage_class: STANDARD_IA
labels:
gl_service: example-service-eks # required
owner_email_handle: my-team
department: eng-infra
department_group: eng-infra-my-group
product_category: my-category
Field Required Default Notes
name yes — Must match ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ (max 45 chars). The physical bucket is runway-<name>-<environment>; the runway- prefix and -<environment> suffix are appended in Terraform. Must be unique across all buckets.
labels yes — Required infrastructure labels. gl_service is required; owner_email_handle, department, department_group, and product_category are optional. See the Infrastructure Standards.
versioning no true Enable object versioning.
abort_incomplete_multipart_upload_days no 7 Abort incomplete multipart uploads after N days, so interrupted uploads don’t accumulate as unlisted, indefinitely-billed orphaned parts. Applied bucket-wide, independent of lifecycle_rules.
lifecycle_rules no [] Bucket lifecycle rules. Each rule has an id (required), enabled (default true), and any combination of expiration.days, noncurrent_version_expiration.days, transition (list of {days, storage_class}), and noncurrent_version_transition. storage_class is one of STANDARD_IA, ONEZONE_IA, INTELLIGENT_TIERING, GLACIER, DEEP_ARCHIVE, GLACIER_IR.

Encryption is always SSE-S3 (AES256) and all public access is blocked — these are fixed and not configurable.

Create the MR and assign it to a Runway team member (see CODEOWNERS), and ensure the pipeline passes before requesting review.

In config/runtimes/eks/workloads.yml, add an s3_buckets key to your workload entry, referencing the bucket by its name:

config/runtimes/eks/workloads.yml
- runway_service_id: example-service-eks
s3_buckets:
- example-bucket

This grants the workload’s IRSA role s3:ListBucket on the bucket, and s3:GetObject/PutObject/DeleteObject/AbortMultipartUpload scoped to objects within it. Remember that each bucket may be referenced by only one workload; the inventory validation fails if two workloads claim the same bucket or if you reference a bucket that is not defined in s3.yml.

Step 3: Declare the bucket in your deployment manifest

Section titled “Step 3: Declare the bucket in your deployment manifest”

In your service’s deployment-eks.yaml, add the bucket under spec.infrastructure.s3_object_store, referencing it by the same name used in s3.yml:

.runway/<service>/deployment-eks.yaml
apiVersion: runway/v2
kind: RunwayManifest
metadata:
name: example-service-eks
spec:
infrastructure:
s3_object_store:
- name: example-bucket

Only one entry is supported today — additional entries are ignored with a warning at generate time.

Step 4: Declare the dependency in your Fairway manifest

Section titled “Step 4: Declare the dependency in your Fairway manifest”

In your service’s fairway.yaml, declare that the service needs an object store, the same way you would declare a Postgres or Redis dependency:

.runway/<service>/fairway.yaml
spec:
infrastructure:
object_store:
presence: REQUIRED

Do not set spec.values.infrastructure.objectStore yourself for a Runway-managed bucket — runwayctl computes the entire block (bucket, region, and secretRef) from the deployment manifest’s s3_object_store entry in Step 3, and overwrites anything you set here. See Declaring an object store dependency in the Fairway guide for the full field model, including the case of a bucket Runway does not manage.

On apply, the provisioner creates, for each environment:

  • The bucket runway-<name>-<environment>, with all public access blocked, SSE-S3 encryption, and the versioning/lifecycle configuration you declared.

  • An IAM policy on the workload’s IRSA role (eks-<runway_service_id>), scoped to the exact bucket and its objects.

  • A secret published to both Vault (mount runway, path platform/<runway_service_id>/s3/<bucket>/<environment>) and AWS Secrets Manager (same path), containing:

    {
    "bucket_name": "runway-example-bucket-production",
    "region": "us-east-1"
    }

Runway’s EKS deployment tooling:

  1. Creates an ExternalSecret that syncs the AWS Secrets Manager secret from What gets created into a Kubernetes Secret in your namespace.
  2. Sets values.infrastructure.objectStore.s3.secretRef to point at that Secret, with a CUSTOM projection mapping the logical bucket/region fields to the Secret’s bucket_name/region keys.
  3. No credentials are projected — your pod’s IRSA role supplies S3 credentials via the AWS SDK’s default credential chain.

Your service reads the bucket name and region through LabKit’s objectstore package, the same way it would for any other object_store dependency — see Declaring an object store dependency in the Fairway guide for what gets mounted and where.

  • Review the S3 schema for the authoritative field definitions.
  • See Secrets management for other secret needs your service may have.
  • Ask the Runway team in #f_runway for help.