Amazon S3 backend
The Amazon S3 backend persists state to an object in an S3 bucket — the shared backend for a team or CI, where many runners need a single source of truth.
Declare it with a BACKEND s3 config block:
BACKEND s3 ( bucket = 'my-bucket', key = 'env/state.json');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. |
Any attribute not listed here is rejected — a typo surfaces as an error rather than being silently ignored.
Credentials and region
Section titled “Credentials and region”AWS credentials and region are resolved through the standard AWS SDK chain — environment variables, shared config/credentials files, and instance/role profiles — so they’re not part of the config block. In CI, supply them the same way you would for any other AWS tooling.
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 backend 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, rather than silently overwriting it. If a run
is interrupted and leaves the lock object behind, clear it with
force-unlock. See Locking.
Using the library
Section titled “Using the library”When embedding the engine instead of the CLI, register the S3 backend
with the NSchema.Aws package:
dotnet add package NSchema.Coredotnet add package NSchema.AwsUseS3StateStore has three overloads:
// 1. Bucket and key directly.builder.UseS3StateStore("my-bucket", "nschema/state.json");
// 2. Configure options via a delegate.builder.UseS3StateStore(o =>{ o.Bucket = configuration["NSchema:Bucket"]!; o.Key = configuration["NSchema:Key"]!;});
// 3. As above, with access to the IServiceProvider.builder.UseS3StateStore((o, sp) =>{ var config = sp.GetRequiredService<IConfiguration>(); o.Bucket = config["NSchema:Bucket"]!; o.Key = config["NSchema:Key"]!;});If no IAmazonS3 is registered, a default AmazonS3Client using ambient credentials (ECS task
role, Lambda execution role, environment variables, …) is registered automatically. To bring
your own client, register it (e.g. via AWSSDK.Extensions.NETCore.Setup) before calling
UseS3StateStore:
builder.Services.AddAWSService<IAmazonS3>();builder.UseS3StateStore("my-bucket", "nschema/state.json");IAM permissions
Section titled “IAM permissions”| Operation | Required permissions |
|---|---|
apply / refresh / destroy (writes state) | s3:GetObject, s3:PutObject, s3:DeleteObject |
plan / 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" ]}