Infrastructure

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:

terminal
$ docker compose up -d
docker-compose.yml
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

ServicePort(s)CredentialsPurpose
PostgreSQL5432grit / gritPrimary database
Redis6379No authCache, sessions, job queues
MinIO9000 / 9001minioadmin / minioadminS3-compatible file storage
Mailhog1025 / 8025No authEmail testing (SMTP + Web UI)

Accessing Services

MinIO Console

http://localhost:9001

Web-based file browser for your S3-compatible storage. Create buckets, upload files, manage access policies. Login with minioadmin / minioadmin.

Mailhog UI

http://localhost:8025

Catches all outgoing emails from your application. View HTML emails, check headers, and test email flows without sending real emails.

PostgreSQL

localhost:5432

Connect using any database client (pgAdmin, TablePlus, DBeaver). Connection string: postgres://grit:grit@localhost:5432/myapp?sslmode=disable

Redis

localhost:6379

Connect 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.

docker-compose.prod.yml
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:

terminal
$ docker compose -f docker-compose.prod.yml up -d --build

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.

apps/api/Dockerfile
# 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.

apps/web/Dockerfile
# 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

terminal
$ docker compose up -d

Start all development services in the background

terminal
$ docker compose down

Stop and remove all containers

terminal
$ docker compose down -v

Stop all containers and delete volumes (resets all data)

terminal
$ docker compose logs -f postgres

Follow logs for a specific service

terminal
$ docker compose logs -f

Follow logs for all services

terminal
$ docker compose ps

Show running containers and their status

terminal
$ docker compose restart redis

Restart a specific service

terminal
$ docker compose exec postgres psql -U grit myapp

Open a psql shell inside the PostgreSQL container

terminal
$ docker compose exec redis redis-cli

Open a Redis CLI session inside the container

Data Persistence

Docker named volumes keep your data safe across container restarts:

VolumeMounted ToContains
postgres-data/var/lib/postgresql/dataAll database tables and data
redis-data/dataCached data, sessions, job queue state
minio-data/dataUploaded 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.