# Set up a bucket

> What the bucket must support, and three ways to get one: AWS S3 with the Terraform module, Tigris, and a local SeaweedFS container.

Canonical URL: https://www.slivingdoc.dev/docs/guides/bucket/ · Version: 0.1 · Updated: 2026-09-21

Full site index: https://www.slivingdoc.dev/llms.txt

A notebook is one bucket plus one prefix. The prefix (default
`slivingdoc`) is the path inside the bucket where the notebook's objects
live, and slivingdoc creates it with the first commit. The bucket itself
must already exist: slivingdoc never creates or configures one.

`my-notes` in these examples is a bucket name. On AWS that name is the
first label of the host — `my-notes.s3.eu-north-1.amazonaws.com` — while
a custom endpoint takes it as a path segment,
`https://t3.storage.dev/my-notes/…`. The region or the endpoint decides
which store answers; the name only decides which container in it.

## What the bucket must support

Before the first MCP call, the server runs a disposable compatibility
probe below the configured prefix. The probe proves that the store
enforces `If-None-Match: *` creation, `If-Match` replacement, and
read-after-write behaviour — the three conditional-write guarantees the
publication protocol depends on. A store that fails the probe is refused
at startup with the `INCOMPATIBLE_STORE` category, and when the failure
is operational rather than a missing capability the diagnostic names the
underlying reason, with the probe key and any secret redacted. Bucket
versioning is not required. See
[S3 requirements](/docs/reference/s3/) for the exact permission set.

Three settings differ per store:

| Setting    | Flag           | Note                                                                |
| ---------- | -------------- | ------------------------------------------------------------------- |
| Endpoint   | `--endpoint`   | Empty for AWS. An absolute `http` or `https` URL for anything else. |
| Region     | `--region`     | `us-east-1` unless you set it.                                      |
| Path style | `--path-style` | Automatic for a custom endpoint; this flag extends it to AWS.       |

A custom endpoint always uses path-style addressing, so `--path-style` is
only needed to force path style on the default AWS endpoint. A
`--endpoint` URL that carries user information is refused, so a secret
can never echo into a diagnostic.

## AWS S3, with the Terraform module

The repository ships a reusable module that provisions the private
bucket and the least-privilege IAM user whose access keys the server
uses:

```hcl
module "notebook" {
  source = "github.com/baalimago/slivingdoc//terraform"

  bucket_name   = "my-notes"
  iam_user_name = "my-notes-mcp"

  tags = {
    purpose = "slivingdoc-notes"
  }
}

output "access_key_id" {
  value     = module.notebook.access_key_id
  sensitive = true
}

output "secret_access_key" {
  value     = module.notebook.secret_access_key
  sensitive = true
}
```

Configure the `aws` provider in your own root module; the module does not
set a region. The `//terraform` suffix selects the subdirectory of the
repository, and no `?ref` means the default branch — pin a release with
`?ref=vX.Y.Z`.

The module creates the bucket with versioning enabled,
`BucketOwnerEnforced` ownership, SSE-S3 default encryption with the
bucket key enabled, SSE-C uploads blocked, and all four public access
blocks on. A bucket policy denies every S3 action to every principal
except the created IAM user and the AWS account root. The root exemption
is deliberate: it is the recovery path, and the only way
`terraform destroy` can complete. The IAM user carries one inline policy
scoped to this bucket alone.

Its inputs are `bucket_name` (required), `iam_user_name` (defaults to the
bucket name), `tags`, and `force_destroy`, which is needed to delete a
bucket that still holds notebook state. Read the keys from the sensitive
outputs:

```text
terraform output -raw access_key_id
terraform output -raw secret_access_key
```

Then point the server at it. The endpoint stays empty, and the region is
the bucket's region:

```text
export SLIVINGDOC_BUCKET=my-notes
slivingdoc serve --region eu-north-1
```

The repository's
[`terraform/README.md`](https://github.com/baalimago/slivingdoc/blob/master/terraform/README.md)
documents the module, and
[`examples/terraform/`](https://github.com/baalimago/slivingdoc/tree/master/examples/terraform)
is a worked root configuration.

## Tigris

Tigris is an S3-compatible store with one global endpoint, and it
supports the conditional writes the startup probe requires. Create a
bucket and an access key pair in its console, then set the endpoint and
the region:

```text
export SLIVINGDOC_BUCKET=my-notes
export AWS_ACCESS_KEY_ID=<your-access-key-id>
export AWS_SECRET_ACCESS_KEY=<your-secret-access-key>

slivingdoc serve \
  --endpoint https://t3.storage.dev \
  --region auto
```

Path-style addressing is automatic for a custom endpoint, so no further
flag is needed.

## Local SeaweedFS, for evaluation

One pinned container gives you an S3-compatible store with no account at
all. The compose file is
[`examples/seaweedfs/`](https://github.com/baalimago/slivingdoc/tree/master/examples/seaweedfs)
in the repository. From that directory:

```text
docker compose up -d
```

The S3 gateway listens on `http://localhost:8333`. The credentials are
fixed in the compose file — `slivingdoc` and `slivingdoc-local` — and
exist only inside that container. There is no bucket step: `my-notes` is
created on the first write, which the startup probe performs, so the
bucket exists by the time the server accepts MCP calls.

```text
export SLIVINGDOC_BUCKET=my-notes
export AWS_ACCESS_KEY_ID=slivingdoc
export AWS_SECRET_ACCESS_KEY=slivingdoc-local

slivingdoc serve \
  --endpoint http://localhost:8333 \
  --path-style \
  --workspace-root /tmp/notes \
  --private-root /tmp/slivingdoc-private
```

The two root flags are optional — without them the server takes its own
temporary notebook directory and removes it at shutdown — but naming them
lets you open the files yourself while following along. See
[Share a directory with humans](/docs/guides/shared-directory/).

> **Note:** The container logs a `no signing key found for STS service`
> line on startup. It is not fatal, and it is safe to ignore for basic
> credentials.

Stop it when you are done:

```text
docker compose down        # stop the container, keep the notes volume
docker compose down -v     # also delete the notes volume
```

> **Warning:** This container is for evaluation on one machine. Its
> credentials are public, in a file in a public repository.

## Next

- [Quickstart](/docs/quickstart/) — the first pull and commit against the
  bucket you just made.
- [S3 requirements](/docs/reference/s3/) — permissions, the probe, and
  what the store must guarantee.
- [Connect an MCP host](/docs/guides/mcp-hosts/) — how the credentials
  reach the server.
