Deploy Kan with PostgreSQL and MinIO (S3-compatible storage) using Docker Compose, with clear steps and production notes.

What you’ll set up

Kan

Kan web app (Next.js), on port 3000.

PostgreSQL 15

PostgreSQL database for Kan.

MinIO (S3-compatible)

MinIO object storage (console port 9001, S3 API port 9000).

How it works

  • Kan stores data in PostgreSQL.
  • Kan uploads files (e.g., avatars) to MinIO over the S3 API.
  • The browser fetches public files directly from MinIO’s public URL.
  • The Next.js image optimizer in Kan must be explicitly allowed to fetch from your storage host.
Key domain settings:
  • NEXT_PUBLIC_BASE_URL → the Kan site
  • NEXT_PUBLIC_STORAGE_URL → the public S3 base URL
  • NEXT_PUBLIC_STORAGE_DOMAIN → the exact S3 hostname (no scheme)

Prerequisites

  • Docker and Docker Compose
  • Open local ports: 3000 (Kan), 5432 (Postgres), 9000/9001 (MinIO)
  • A long random string for BETTER_AUTH_SECRET (32+ chars)
For production you’ll want a reverse proxy (Traefik/Nginx/Caddy), valid TLS certificates, and DNS for your domains (e.g., kan.example.com, s3.example.com).

Quick start

Why localtest.me? It resolves to 127.0.0.1 automatically, so you can test domain-based configs locally without editing hosts.
1

Set environment variables

Provide the minimum required configuration (local example):
NEXT_PUBLIC_BASE_URL=http://kan.localtest.me:3000
BETTER_AUTH_SECRET=<long random string>
POSTGRES_URL=postgresql://kan:<password>@postgres:5432/kan_db

# MinIO/S3
S3_ENDPOINT=http://s3.localtest.me:9000
S3_ACCESS_KEY_ID=<minio-access-key>
S3_SECRET_ACCESS_KEY=<minio-secret-key>
S3_REGION=none
S3_FORCE_PATH_STYLE=true

# Public storage access
NEXT_PUBLIC_STORAGE_URL=http://s3.localtest.me:9000
NEXT_PUBLIC_STORAGE_DOMAIN=s3.localtest.me
NEXT_PUBLIC_AVATAR_BUCKET_NAME=kan
Issue #109 fix: make sure NEXT_PUBLIC_STORAGE_DOMAIN exactly equals the hostname that serves your images (no scheme, no port).
Optional (see README for full list): Email (EMAIL_FROM, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_SECURE), OAuth/OIDC (GOOGLE_*, GITHUB_*, OIDC_*), auth toggles, Trello import, etc.
2

Create or review Docker Compose files

You can start from the minimal compose at the repository root (docker-compose.yml) and review the production-oriented settings in cloud/docker-compose.yml.Start with the minimal setup (web + postgres + minio) and ensure environment variables are passed to the web service.
3

Start services

docker compose up -d
Then open:
4

Initialize MinIO

  1. Log into the MinIO Console (http://minio.localtest.me:9001).
  1. Create a bucket (e.g., kan).
  2. For simple public avatars, apply a read-only policy so GET requests are allowed for objects:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": ["*"] },
      "Action": ["s3:GetBucketLocation", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::kan"]
    },
    {
      "Effect": "Allow",
      "Principal": { "AWS": ["*"] },
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::kan/*"]
    }
  ]
}
Alternatively, keep the bucket private and use presigned URLs. In that case, ensure your server and browser access paths are correctly configured.
5

Verify the setup

  • Sign in to Kan and upload an avatar (Settings).
  • Confirm the object is created in your MinIO bucket.
  • The avatar should render without errors.
If you see a 400 from /_next/image with “url parameter is not allowed”:
  • NEXT_PUBLIC_STORAGE_DOMAIN must exactly match the S3 hostname that serves images.
  • NEXT_PUBLIC_STORAGE_URL should use the same host (with scheme/port).
  • Ensure you’re using the latest Kan image.

Production setup

Troubleshooting

Make the bucket public (read-only) with mc

References