Amazon S3
The Amazon S3 backend persists state to an object in an S3 bucket. This is ideal for a shared backend for a team or CI, where many runners need a single source of truth.
Declare the plugin, then point a STATE statement at it:
PLUGIN s3 ( source = 'NSchema.Aws', version = '[5.0,6.0)');
STATE s3 ( bucket = 'my-bucket', key = 'env/state.json');The label (s3 here) is yours to choose; the STATE statement selects the plugin by referencing it. The package is
resolved and locked by init and restored on first use.
Attributes
Section titled “Attributes”| Attribute | Type | Description |
|---|---|---|
bucket |
string | The S3 bucket that holds the state object. |
key |
string | The S3 object key for the state file within the bucket. |
force_path_style |
boolean | Use path-style addressing (endpoint/bucket/key) instead of the host (bucket.endpoint/key). Defaults to false. |
Configuration
Section titled “Configuration”The endpoint, credentials, and region are resolved through the standard AWS SDK chain: environment variables (AWS_ENDPOINT_URL,
AWS_REGION, etc.), shared config/credentials files, and instance/role profiles, so they’re not part of the statement.
In CI, supply them the same way you would for any other AWS tooling.
S3-compatible stores
Section titled “S3-compatible stores”To target an S3-compatible store such as MinIO, point AWS_ENDPOINT_URL_S3 at it and set
force_path_style = true in the statement. Path-style addressing is the one setting with no standard environment
variable, so it lives on the statement; everything else still comes from the ambient AWS configuration:
STATE s3 ( bucket = 'my-bucket', key = 'env/state.json', force_path_style = true);When to use it
Section titled “When to use it”Reach for S3 when more than one machine needs to read and write the same state: a team sharing a project, or a CI pipeline whose plan and apply steps run on different runners. For a single operator, the simpler local file state is usually enough.
Locking
Section titled “Locking”The S3 backend coordinates exclusive access through a sibling lock object (the state key plus a .lock suffix), created
atomically with an S3 conditional write, so a second writer is rejected while another operation holds the lock. If a run
is interrupted and leaves the lock object behind, clear it with lock release. See Locking.
IAM permissions
Section titled “IAM permissions”| Operation | Required permissions |
|---|---|
apply / refresh / destroy (writes state) |
s3:GetObject, s3:PutObject, s3:DeleteObject |
plan / state show / drift (reads state) |
s3:GetObject |
The lock object lives beside the state object, so write operations also need s3:PutObject and
s3:DeleteObject on the .lock key. A policy scoped to both:
{ "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": [ "arn:aws:s3:::my-bucket/nschema/state.json", "arn:aws:s3:::my-bucket/nschema/state.json.lock" ]}