Docker Setup
Grit uses Docker Compose to run all infrastructure services locally. One command gives you PostgreSQL, Redis, MinIO, and Mailhog — ready to go.
Overview
Every Grit project ships with two Docker Compose files:
- ✓
docker-compose.yml— Development infrastructure (databases, caching, storage, email) - ✓
docker-compose.prod.yml— Production deployment (includes API, web, admin containers)
You do not need Docker to run the Go API or Next.js apps directly — Docker is only required for the infrastructure services (PostgreSQL, Redis, etc.). The API and frontends run natively during development for faster iteration.
Development Compose File
The development docker-compose.yml spins up four services. Start them all with a single command:
services:
postgres:
image: postgres:16-alpine
container_name: myapp-postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: grit
POSTGRES_PASSWORD: grit
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U grit"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
minio:
image: minio/minio
container_name: myapp-minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- minio-data:/data
command: server /data --console-address ":9001"
mailhog:
image: mailhog/mailhog
container_name: myapp-mailhog
restart: unless-stopped
ports:
- "1025:1025"
- "8025:8025"
volumes:
postgres-data:
redis-data:
minio-data:Service Details
| Service | Port(s) | Credentials | Purpose |
|---|---|---|---|
| PostgreSQL | 5432 | grit / grit | Primary database |
| Redis | 6379 | No auth | Cache, sessions, job queues |
| MinIO | 9000 / 9001 | minioadmin / minioadmin | S3-compatible file storage |
| Mailhog | 1025 / 8025 | No auth | Email testing (SMTP + Web UI) |
Accessing Services
MinIO Console
http://localhost:9001Web-based file browser for your S3-compatible storage. Create buckets, upload files, manage access policies. Login with minioadmin / minioadmin.
Mailhog UI
http://localhost:8025Catches all outgoing emails from your application. View HTML emails, check headers, and test email flows without sending real emails.
PostgreSQL
localhost:5432Connect using any database client (pgAdmin, TablePlus, DBeaver). Connection string: postgres://grit:grit@localhost:5432/myapp?sslmode=disable
Redis
localhost:6379Connect with redis-cli or any Redis GUI client (RedisInsight, Medis). No authentication required in development.
Production Compose File
The docker-compose.prod.yml builds and runs your entire application stack including the Go API, Next.js web app, and admin panel alongside PostgreSQL and Redis.
services:
api:
build:
context: ./apps/api
dockerfile: Dockerfile
container_name: myapp-api
restart: unless-stopped
ports:
- "8080:8080"
environment:
APP_ENV: production
DATABASE_URL: postgres://grit:grit@postgres:5432/myapp?sslmode=disable
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
web:
build:
context: .
dockerfile: apps/web/Dockerfile
container_name: myapp-web
restart: unless-stopped
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_API_URL: http://api:8080
admin:
build:
context: .
dockerfile: apps/admin/Dockerfile
container_name: myapp-admin
restart: unless-stopped
ports:
- "3001:3000"
environment:
NEXT_PUBLIC_API_URL: http://api:8080
postgres:
image: postgres:16-alpine
container_name: myapp-postgres
restart: unless-stopped
environment:
POSTGRES_USER: grit
POSTGRES_PASSWORD: grit
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U grit"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: unless-stopped
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres-data:
redis-data:Deploy to production with:
Dockerfiles
Go API (Multi-Stage Build)
The API Dockerfile uses a multi-stage build. The first stage compiles the Go binary with all dependencies, and the second stage copies only the binary into a minimal Alpine image. The final image is typically under 20MB.
# Build stage FROM golang:1.21-alpine AS builder WORKDIR /app # Copy go mod files COPY go.mod go.sum ./ RUN go mod download # Copy source code COPY . . # Build binary RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server # Run stage FROM alpine:3.19 RUN apk --no-cache add ca-certificates tzdata WORKDIR /app COPY --from=builder /app/server . EXPOSE 8080 CMD ["./server"]
Next.js (Standalone Build)
The Next.js Dockerfile also uses a multi-stage build. It installs dependencies, builds the app with standalone output, and runs the production server as a non-root user. Both the web and admin apps share this same Dockerfile pattern.
# Build stage FROM node:20-alpine AS base RUN corepack enable # Install dependencies FROM base AS deps WORKDIR /app COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY apps/web/package.json ./apps/web/ COPY packages/shared/package.json ./packages/shared/ RUN pnpm install --frozen-lockfile # Build FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules COPY . . RUN pnpm --filter web build # Run FROM base AS runner WORKDIR /app ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/apps/web/.next/standalone ./ COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static COPY --from=builder /app/apps/web/public ./apps/web/public USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["node", "apps/web/server.js"]
Common Commands
Start all development services in the background
Stop and remove all containers
Stop all containers and delete volumes (resets all data)
Follow logs for a specific service
Follow logs for all services
Show running containers and their status
Restart a specific service
Open a psql shell inside the PostgreSQL container
Open a Redis CLI session inside the container
Data Persistence
Docker named volumes keep your data safe across container restarts:
| Volume | Mounted To | Contains |
|---|---|---|
| postgres-data | /var/lib/postgresql/data | All database tables and data |
| redis-data | /data | Cached data, sessions, job queue state |
| minio-data | /data | Uploaded files and bucket data |
Use docker compose down -v to delete all volumes and reset to a clean state. This is useful when you want to start fresh or if your database schema has diverged.
Troubleshooting
Port already in use
Another process is using port 5432, 6379, etc. Stop the conflicting process or change the port mapping in docker-compose.yml. For example, change "5432:5432" to "5433:5432" and update your .env DATABASE_URL accordingly.
Container keeps restarting
Check the logs with "docker compose logs <service>". Common causes: incorrect credentials, corrupted volume data. Try "docker compose down -v && docker compose up -d" for a clean start.
Cannot connect from API to PostgreSQL
Ensure the API uses "localhost" (not the container name) when running outside Docker. The connection string in .env should be: postgres://grit:grit@localhost:5432/myapp?sslmode=disable
MinIO bucket not found
MinIO starts with no buckets. Open the MinIO console at http://localhost:9001, login with minioadmin/minioadmin, and create your bucket. Or set MINIO_DEFAULT_BUCKETS in the compose file.
Docker Compose V1 vs V2
Grit uses "docker compose" (V2, no hyphen). If you see errors, make sure Docker Desktop is updated. The old "docker-compose" (V1, with hyphen) is deprecated.