Skip to content

S3 Configuration#

This guide covers how to configure S3-compatible object storage for integration testing with tomato.

Overview#

The s3 resource talks to any S3-compatible endpoint. In tests that usually means MinIO or LocalStack, but the same resource works against a real bucket if you point it at one.

Because it speaks the S3 protocol rather than a vendor API, one set of steps covers all three.

Container Setup#

MinIO#

MinIO needs an explicit server command — the image's default entrypoint only prints help.

containers:
  minio:
    image: minio/minio:latest
    command: ["server", "/data"]
    ports:
      - "9000/tcp"
    env:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    wait_for:
      type: port
      target: "9000"
      timeout: 30s

LocalStack#

containers:
  localstack:
    image: localstack/localstack:3
    ports:
      - "4566/tcp"
    env:
      SERVICES: s3
    wait_for:
      type: port
      target: "4566"
      timeout: 90s

Resource Configuration#

resources:
  files:
    type: s3
    container: minio
    options:
      buckets:
        - uploads
        - reports

minio is accepted as an alias for type: s3.

The endpoint is derived from the container automatically. Tomato probes port 9000 (MinIO) and then 4566 (LocalStack), so neither image needs a port option.

Options#

Option Default Description
endpoint (from container) Explicit endpoint, e.g. localhost:9000. Scheme is optional.
port 9000 then 4566 Container port to resolve the endpoint from.
region us-east-1 AWS region.
access_key minioadmin Access key ID.
secret_key minioadmin Secret access key.
use_ssl false Use https:// when building the endpoint.
force_path_style true Path-style addressing. Required by MinIO and LocalStack.
buckets (none) Buckets created at startup, before any scenario runs.
reset_strategy purge purge, delete, or none. See below.
reset_buckets (all buckets) Limit reset to these buckets.
reset_exclude (none) Buckets reset never touches.

Connecting Without a Container#

Point the resource at an endpoint directly to test against something tomato does not manage:

resources:
  files:
    type: s3
    options:
      endpoint: "https://s3.eu-central-1.amazonaws.com"
      region: eu-central-1
      use_ssl: true
      force_path_style: false
      access_key: "${AWS_ACCESS_KEY_ID}"
      secret_key: "${AWS_SECRET_ACCESS_KEY}"
      reset_strategy: none

Reset deletes objects

Reset is destructive by design. Against a real account, set reset_strategy: none or scope it with reset_buckets.

Reset Behaviour#

Following tomato's clean-state principle, every scenario starts with empty storage.

Strategy Behaviour
purge (default) Deletes every object, keeps the buckets.
delete Deletes the buckets entirely, then recreates those listed in buckets.
none Leaves storage untouched.

By default reset covers every bucket on the endpoint, not just the ones in buckets. That way buckets your application creates at runtime are cleaned up too. Narrow it with reset_buckets, or protect individual buckets with reset_exclude:

resources:
  files:
    type: s3
    container: minio
    options:
      buckets: [uploads]
      reset_exclude: [fixtures]   # seeded once in a before_all hook

To keep storage across scenarios, either set reset_strategy: none or opt the resource out of reset entirely:

resources:
  files:
    type: s3
    container: minio
    reset: false

Wiring Your Application#

Pass the endpoint to the application under test with container templates:

app:
  command: go run ./cmd/server
  port: 8080
  env:
    S3_ENDPOINT: "http://{{.minio.host}}:{{.minio.port.9000}}"
    S3_ACCESS_KEY: minioadmin
    S3_SECRET_KEY: minioadmin
    S3_FORCE_PATH_STYLE: "true"

Object Paths#

Every object step takes a single bucket/key path. An s3:// prefix is stripped if present.

Given "files" object "uploads/2026/01/report.pdf" is "..."

Writing to a bucket that does not exist creates it, so seeding steps do not need a bucket ... exists line first.

Example#

Feature: Report export

  Scenario: Exporting writes a report to S3
    Given "db" table "orders" with values:
      | id | total |
      | 1  | 42.00 |
    When "api" sends "POST" request to "/exports"
    Then "api" response code should be "202"
    And "files" object "reports/orders.csv" should exist within "10s"
    And "files" object "reports/orders.csv" content should contain "42.00"

The within steps matter here: applications usually upload in the background, so a plain should exist would race the write.

See Also#