MinIO added to docker-compose: local S3-compatible storage, no R2 credentials needed

juanjo adds MinIO to `docker-compose.yml` so a fresh clone can run end-to-end without real Cloudflare R2 credentials. The storage client change is six lines in `backend/src/lib/storage.ts`; the rest is compose services, env defaults, and docs.

infrastructureintegration

The compose file gains two services (ef16e3a). minio runs minio/minio:latest with the S3 API on port 9000 and the web console on 9001, persisted to the named volume mike_minio_data. Credentials are parameterised via ${MINIO_ROOT_USER:-minioadmin} to match the existing Postgres service convention. minio-init is a one-shot minio/mc:latest sidecar that waits on MinIO's healthcheck then runs mc mb --ignore-existing local/mike to create the bucket. It exits 0 on success - that Exited (0) status in docker compose ps is the expected success state, not an error.

The actual code change (ea0707d) adds two env vars to backend/src/lib/storage.ts's getClient():

region: process.env.R2_REGION ?? "auto",
forcePathStyle: process.env.R2_FORCE_PATH_STYLE === "true",

Both default to their existing R2-correct values, so production behaviour is unchanged when the vars are unset. For MinIO you set R2_REGION to the N. Virginia default and R2_FORCE_PATH_STYLE=true; the latter matters because <bucket>.localhost:9000 has no wildcard DNS, so path-style addressing is required.

The env example files are updated to ship MinIO defaults (720353f). frontend/src/lib/storage.ts is deleted (2a1cc6f) - it was an unused 132-line duplicate of the backend module with no importers. Its associated @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner frontend dependencies are removed (aad90da), which also surfaces a next/opennextjs peer-dep conflict that's tracked in docs/deferred.md.

A follow-up cleanup commit (596ee51) removes the resend package from both packages, adds a note that LLM keys are optional for first-run exploration, and adds a --legacy-peer-deps note to the frontend install instructions.

Two known issues are documented in the ADR: minio/minio:latest and minio/mc:latest are unpinned image tags (MinIO has had breaking releases before), and the R2_* env var prefix is now technically a misnomer but renaming was deferred.

So what Worth a look if you want a `docker compose up` local dev environment with no external storage account. The storage.ts change is low-risk - two env vars with R2-preserving defaults. The unpinned MinIO image tags are a deferred item worth addressing before you have more than a handful of contributors.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

12 commits from juanjo/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
f576fca1 docs: spec for MinIO local S3-compatible storage Juan Vidal 2026-05-08 ↗ GitHub
Add MinIO + bucket-init sidecar to docker-compose; make storage.ts
provider-agnostic via R2_REGION and R2_FORCE_PATH_STYLE; delete the
unused frontend storage duplicate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c5d06a24 docs: implementation plan for MinIO local storage Juan Vidal 2026-05-08 ↗ GitHub
Six tasks: compose changes, env defaults, storage.ts provider-agnostic
client, delete unused frontend duplicate, README docs, manual smoke
test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ef16e3a6 feat(compose): add MinIO and bucket-init for local S3 storage Juan Vidal 2026-05-08 ↗ GitHub
commit body
Adds a minio service (S3 API on :9000, console on :9001) and a
one-shot minio/mc sidecar that creates the 'mike' bucket on first
compose-up. Persistence via mike_minio_data named volume.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cf2effd2 chore(compose): parameterize minio credentials, document init exit-0 Juan Vidal 2026-05-08 ↗ GitHub
commit body
Use ${MINIO_ROOT_USER:-minioadmin} / ${MINIO_ROOT_PASSWORD:-minioadmin}
in both the minio env block and the minio-init entrypoint, matching
the postgres service convention. Adds a comment so contributors don't
mistake minio-init's Exited (0) for an error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
720353fb chore(env): default R2_* vars to local MinIO Juan Vidal 2026-05-08 ↗ GitHub
commit body
Updates root and backend .env.example with MinIO endpoint and dev
credentials, plus the new R2_REGION and R2_FORCE_PATH_STYLE knobs.
Prod R2 config is unaffected - both new vars default to R2-friendly
values when unset in the storage client.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ea0707d6 feat(storage): make S3 client region and path-style env-driven Juan Vidal 2026-05-08 ↗ GitHub
commit body
R2_REGION (default 'auto') and R2_FORCE_PATH_STYLE (default false)
preserve current R2 prod behavior when unset, and let MinIO drop in
for local dev (region 'us-east-1', forcePathStyle true).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2a1cc6ff chore(frontend): remove unused storage.ts duplicate Juan Vidal 2026-05-08 ↗ GitHub
commit body
frontend/src/lib/storage.ts had zero importers - a leftover copy of
the backend module. Storage credentials only ever live on the server;
the frontend gets a signed URL from the backend and never holds keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8c2a0737 docs(readme): document local MinIO setup Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aad90dab chore(frontend): drop unused AWS SDK dependencies Juan Vidal 2026-05-08 ↗ GitHub
commit body
The frontend storage.ts duplicate was removed in 2a1cc6f, leaving
@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner unreferenced.
Uninstalling shrinks the dep tree and prevents accidental future
imports of S3 SDKs into client-bundled code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9aeaae61 docs: ADR-0002, runbooks, and deferred items for MinIO Juan Vidal 2026-05-08 ↗ GitHub
commit body
- ADR-0002 captures the MinIO + provider-agnostic-storage decision,
  consequences, and alternatives, in the same shape as ADR-0001.
- local-development runbook: drop the "needs an R2 account" prereq,
  document the MinIO services in step 1, note that the pre-populated
  R2_* env block targets local MinIO out of the box.
- architecture runbook: storage line now mentions MinIO locally; add
  lib/storage.ts to the backend file map.
- deferred.md: pin MinIO image tags, R2_* → S3_* rename, frontend
  lint baseline, and the next/opennextjs peer-dep conflict that
  surfaced when uninstalling unused AWS SDK deps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
596ee51f chore: drop unused resend dep, smooth local-dev onboarding Juan Vidal 2026-05-08 ↗ GitHub
commit body
- Uninstall the 'resend' package from both backend and frontend.
  Zero source references in either tree; RESEND_API_KEY was env-only
  ceremony for a feature that doesn't exist yet (email is in
  docs/deferred.md as a future item).
- Drop RESEND_API_KEY from backend/.env.example and add a comment
  clarifying that LLM keys are optional for first-run exploration.
- Runbook updates:
  - Prerequisites: LLM keys marked optional with what works without one.
  - Step 1: note that db/init/01-extensions.sql enables pgcrypto on
    first Postgres boot.
  - Step 3: document --legacy-peer-deps requirement for frontend
    npm install (cross-references the deferred item).
  - Step 5: drop Resend from the fillables list, clarify LLM keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3709a5a6 Merge pull request #1 from juanjo/feat/local-mike Juan Vidal 2026-05-08 ↗ GitHub
Make Mike runnable locally: Postgres + Auth.js + MinIO

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-187.md from inside the repo you want the changes in.

⬇ Download capture-thread-187.md