S3
Runway can provision an Amazon S3 bucket in the AWS account of every Dedicated tenant that enables your module, and wire it into your Helm chart through the Fairway infrastructure.objectStore contract.
Runway owns the bucket: runwayctl dedicated provision creates it, and runwayctl dedicated deploy tells your workload where to find it.
You don’t create the bucket or its secret yourself.
This page assumes your module is set up for Dedicated. If it isn’t, start with Getting Started.
Overview
Section titled “Overview”A bucket is configured in three places, and each setting comes from the first one that sets it:
- The tenant’s override in the Tenant Model, under
modular_features.modules.<module-name>.values_override.infrastructure.s3.<name>. - Your default in the module manifest, under
spec.infrastructure.s3.<name>.defaultConfig, whichcatalog createcopies into the Module Catalog entry. - The built-in default in the field reference.
<name> is the bucket’s logical name, a name of your choice that the module manifest and the Tenant Model share.
Entries are merged by logical name and then field by field, so a tenant only sets what differs.
Whether your module needs a bucket at all is its object_store presence in the Fairway manifest.
Step 1: Declare the dependency in your Fairway manifest
Section titled “Step 1: Declare the dependency in your Fairway manifest”spec: infrastructure: object_store: presence: REQUIRED| Presence | Module manifest | Tenant Model, after the merge |
|---|---|---|
REQUIRED |
Needs at least one enabled default. | Needs exactly one enabled bucket. |
OPTIONAL |
Any defaults, enabled or not, or none. | At most one enabled bucket. |
UNUSED |
Must have no defaults, not even disabled ones. | Must have no enabled bucket. |
catalog create checks the module manifest against the presence when you release the module.
provision and deploy check the merged result on every tenant.
Don’t set spec.values.infrastructure.objectStore.s3.secretRef yourself.
Runway sets it, and overwrites anything you put there with a warning.
It also replaces a GCS backend, with a warning.
Other objectStore settings, such as name, are yours to set and are kept.
Step 2: Set the defaults in your module manifest
Section titled “Step 2: Set the defaults in your module manifest”apiVersion: runway/v3kind: RunwayManifestmetadata: name: example-service-dedicatedspec: infrastructure: s3: uploads: defaultConfig: abortIncompleteMultipartUploadDays: 3 lifecycleRules: set: - id: expire-tmp expiration: days: 30A defaultConfig must be valid on its own, without any override, including when it is disabled.
defaultConfig: {} is valid and gives the built-in defaults.
Lifecycle rules must use set: add is only meaningful in the Tenant Model.
Field names can be in camelCase or snake_case.
Step 3: Override per tenant, if needed
Section titled “Step 3: Override per tenant, if needed”A tenant that is happy with your defaults needs no infrastructure block.
To change a setting for one tenant, set only that field:
{ "modular_features": { "modules": { "example-service-dedicated": { "enabled": true, "version": "v1.26.0", "values_override": { "infrastructure": { "s3": { "uploads": { "versioning": false } } } } } } }}The override is partial: it only has to parse, with the right types and no unknown fields. The merged bucket is validated as a complete configuration, where an unset field stands for its built-in default. A tenant can also add a bucket that has no default, which must then be complete on its own.
For this tenant, the bucket gets versioning: false from the Tenant Model, abort_incomplete_multipart_upload_days: 3 and the expire-tmp rule from the module manifest, and enabled: true from the built-in defaults.
Field reference
Section titled “Field reference”InfrastructureConfigS3 in runwayctl’s s3.proto.
Unknown fields are rejected.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Provisions the bucket. See Enabling and disabling. |
versioning |
boolean | true |
Enables object versioning. |
abort_incomplete_multipart_upload_days |
integer | 7 |
Aborts incomplete multipart uploads after this many days. At least 1. |
lifecycle_rules |
object | none | Lifecycle rules of the bucket. |
Logical names are lowercase letters, digits, and hyphens, starting and ending with a letter or digit, at most 45 characters.
Lifecycle rules
Section titled “Lifecycle rules”lifecycle_rules has two lists, set and add.
Setting both is an error.
| Override | Result |
|---|---|
lifecycle_rules unset |
The default rules are kept. |
set with rules |
The default rules are replaced. |
set: [], add: [], or lifecycle_rules: {} |
The bucket has no rules. |
add with rules |
The rules are appended to the default rules, or are the only rules if there are no defaults. |
set and add both with rules |
Error. |
Rules are replaced or appended as a whole, never merged by id.
To change one default rule, replace the whole list with set.
Rule IDs must be unique in the merged result, so an appended rule can’t reuse the ID of a default rule.
An empty add: [] clears the list, just like set: []: an empty list can’t be told apart from an unset one.
{ "s3": { "uploads": { "lifecycle_rules": { "add": [ { "id": "archive", "transitions": [ { "days": 30, "storage_class": "STANDARD_IA" }, { "days": 90, "storage_class": "GLACIER_IR" } ] } ] } } }}Each rule:
| Field | Type | Description |
|---|---|---|
id |
string | Required. Rule ID. abort-incomplete-multipart-uploads is reserved for the rule that abort_incomplete_multipart_upload_days adds. |
enabled |
boolean | Enables the rule. Defaults to true. |
expiration.days |
integer | Deletes current-version objects after this many days. days is required if expiration is set. |
noncurrent_version_expiration.days |
integer | Deletes noncurrent (superseded) versions after this many days. days is required if noncurrent_version_expiration is set. |
transitions |
array | Moves current-version objects to another storage class. |
noncurrent_version_transitions |
array | Moves noncurrent versions to another storage class. |
Transitions
Section titled “Transitions”Each entry of transitions and noncurrent_version_transitions needs both fields:
| Field | Type | Description |
|---|---|---|
days |
integer | Moves objects after this many days. |
storage_class |
string | One of STANDARD_IA, ONEZONE_IA, INTELLIGENT_TIERING, GLACIER, DEEP_ARCHIVE, GLACIER_IR. |
Enabling and disabling
Section titled “Enabling and disabling”Every bucket has an enabled field.
Unset means enabled.
- Off by default: set
enabled: falsein thedefaultConfig. The defaults are still checked, so that they are sane when a tenant turns the bucket on withenabled: true, without repeating the other settings. AREQUIREDobject store can’t be off by default. - Off for one tenant: the tenant sets
enabled: false. Runway doesn’t provision the bucket, deploys noSecretand no Helm values for it, and doesn’t check its other settings. For aREQUIREDobject store, this is an error.
What gets created
Section titled “What gets created”provision creates, in the tenant’s AWS account and region:
- The bucket
<tenant-id>-<module-name>-<name>-<digest>. The digest is the first 8 hex characters of the SHA-256 of<account>/<tenant-id>/<module-name>/<name>, so two tenants or modules never collide. If the name exceeds 63 characters,<module-name>-<name>is shortened, keeping the tenant prefix and the digest. The bucket is encrypted with the tenant’s customer-managed S3 key from the Tenant Model, or with SSE-S3 if it has none. - The secret
runway/<module-name>/object_store/<name>in AWS Secrets Manager, with the keysbucketandregion.
deploy then:
- writes the Kubernetes
Secrets3-<name>in the module’s namespace, with the keysbucketandregion; - points
infrastructure.objectStore.s3.secretRefin your Helm values at it.
The Secret holds no credentials: workloads use the AWS SDK default credential chain.