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.

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 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:

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:

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:

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

The repository’s terraform/README.md documents the module, and 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:

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/ in the repository. From that directory:

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.

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.

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:

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

Last updated September 21, 2026

Type to search the documentation.