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-alpinecontainer_name: myapp-postgresrestart: unless-stoppedports:- "5432:5432"environment:POSTGRES_USER: gritPOSTGRES_PASSWORD: gritPOSTGRES_DB: myappvolumes:- postgres-data:/var/lib/postgresql/datahealthcheck:test: ["CMD-SHELL", "pg_isready -U grit"]interval: 5stimeout: 5sretries: 5redis:image: redis:7-alpinecontainer_name: myapp-redisrestart: unless-stoppedports:- "6379:6379"volumes:- redis-data:/datahealthcheck:test: ["CMD", "redis-cli", "ping"]interval: 5stimeout: 5sretries: 5minio:image: minio/miniocontainer_name: myapp-miniorestart: unless-stoppedports:- "9000:9000"- "9001:9001"environment:MINIO_ROOT_USER: minioadminMINIO_ROOT_PASSWORD: minioadminvolumes:- minio-data:/datacommand: server /data --console-address ":9001"mailhog:image: mailhog/mailhogcontainer_name: myapp-mailhogrestart: unless-stoppedports:- "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/apidockerfile: Dockerfilecontainer_name: myapp-apirestart: unless-stoppedports:- "8080:8080"environment:APP_ENV: productionDATABASE_URL: postgres://grit:grit@postgres:5432/myapp?sslmode=disableREDIS_URL: redis://redis:6379JWT_SECRET: ${JWT_SECRET}depends_on:postgres:condition: service_healthyredis:condition: service_healthyweb:build:context: .dockerfile: apps/web/Dockerfilecontainer_name: myapp-webrestart: unless-stoppedports:- "3000:3000"environment:NEXT_PUBLIC_API_URL: http://api:8080admin:build:context: .dockerfile: apps/admin/Dockerfilecontainer_name: myapp-adminrestart: unless-stoppedports:- "3001:3000"environment:NEXT_PUBLIC_API_URL: http://api:8080postgres:image: postgres:16-alpinecontainer_name: myapp-postgresrestart: unless-stoppedenvironment:POSTGRES_USER: gritPOSTGRES_PASSWORD: gritPOSTGRES_DB: myappvolumes:- postgres-data:/var/lib/postgresql/datahealthcheck:test: ["CMD-SHELL", "pg_isready -U grit"]interval: 5stimeout: 5sretries: 5redis:image: redis:7-alpinecontainer_name: myapp-redisrestart: unless-stoppedvolumes:- redis-data:/datahealthcheck:test: ["CMD", "redis-cli", "ping"]interval: 5stimeout: 5sretries: 5volumes: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 stageFROM golang:1.21-alpine AS builderWORKDIR /app# Copy go mod filesCOPY go.mod go.sum ./RUN go mod download# Copy source codeCOPY . .# Build binaryRUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server# Run stageFROM alpine:3.19RUN apk --no-cache add ca-certificates tzdataWORKDIR /appCOPY --from=builder /app/server .EXPOSE 8080CMD ["./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 stageFROM node:20-alpine AS baseRUN corepack enable# Install dependenciesFROM base AS depsWORKDIR /appCOPY 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# BuildFROM base AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY --from=deps /app/apps/web/node_modules ./apps/web/node_modulesCOPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modulesCOPY . .RUN pnpm --filter web build# RunFROM base AS runnerWORKDIR /appENV NODE_ENV=productionRUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 nextjsCOPY --from=builder /app/apps/web/.next/standalone ./COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/staticCOPY --from=builder /app/apps/web/public ./apps/web/publicUSER nextjsEXPOSE 3000ENV PORT=3000ENV 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.