Skip to content

Docker Setup

This guide explains how to use Docker to run the Goals Tracker application.

Docker Architecture

The application consists of three main services:

┌─────────────────────────────────────────────┐
│            Docker Compose                   │
├─────────────────────────────────────────────┤
│  ┌───────────────┐  ┌──────────────────┐   │
│  │   Frontend    │  │     Backend      │   │
│  │  React:5173   │  │  Spring:8080     │   │
│  └───────┬───────┘  └────────┬─────────┘   │
│          │                    │             │
│          └──────────┬─────────┘             │
│                     │                       │
│          ┌──────────▼──────────┐            │
│          │     PostgreSQL      │            │
│          │       :5432         │            │
│          └─────────────────────┘            │
└─────────────────────────────────────────────┘

Docker Compose Configuration

docker-compose.yml

services:
  db:
    image: postgres:14
    restart: always
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: goals_tracker_db
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5

  goals-tracker-backend:
    build:
      context: ./goals-tracker-back
      dockerfile: Dockerfile
    restart: always
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      DATABASE_USER: user
      DATABASE_PASSWORD: password
      DATABASE_NAME: goals_tracker_db
      JWT_SECRET: your-secret-key-change-in-production
      JWT_EXPIRATION: 86400000
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  goals-tracker-front:
    build:
      context: ./goals-tracker-front
      dockerfile: Dockerfile
    restart: always
    environment:
      VITE_API_URL: http://localhost:8080
    ports:
      - "5173:5173"
    depends_on:
      - goals-tracker-backend

volumes:
  pgdata:

Backend Dockerfile

Located at goals-tracker-back/Dockerfile:

# Multi-stage build for optimized image size

# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS build

WORKDIR /app

# Copy dependency files first (layer caching)
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .

# Download dependencies
RUN ./mvnw dependency:go-offline -B

# Copy source code
COPY src ./src

# Build the application (skip tests for faster builds)
RUN ./mvnw clean package -DskipTests

# Stage 2: Runtime
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# Copy the JAR from build stage
COPY --from=build /app/target/*.jar app.jar

# Create non-root user
RUN addgroup -g 1001 -S appuser && \
    adduser -u 1001 -S appuser -G appuser

USER appuser

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]

Build Optimization

Layer Caching: Dependencies are cached separately from source code Multi-stage Build: Only runtime dependencies in final image Alpine Base: Smaller image size (~150MB vs ~400MB) Non-root User: Security best practice

Frontend Dockerfile

Located at goals-tracker-front/Dockerfile:

# Stage 1: Build
FROM node:18-alpine AS build

WORKDIR /app

# Copy dependency files
COPY package.json package-lock.json ./

# Install dependencies
RUN npm ci

# Copy source code
COPY . .

# Build the application
RUN npm run build

# Stage 2: Production with Nginx
FROM nginx:alpine

# Copy built assets from build stage
COPY --from=build /app/dist /usr/share/nginx/html

# Copy custom nginx config (optional)
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Alternative: Development Mode

For development with hot reload:

FROM node:18-alpine

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .

EXPOSE 5173

CMD ["npm", "run", "dev", "--", "--host"]

Docker Commands

Basic Operations

Start all services:

docker compose up

Start with rebuild:

docker compose up --build

Start in detached mode (background):

docker compose up -d

Stop all services:

docker compose down

Stop and remove volumes (deletes data):

docker compose down -v

Individual Services

Start only database:

docker compose up db

Start backend and its dependencies:

docker compose up goals-tracker-backend

Rebuild a specific service:

docker compose up --build goals-tracker-backend

Viewing Logs

View all logs:

docker compose logs

Follow logs (live):

docker compose logs -f

View logs for specific service:

docker compose logs goals-tracker-backend
docker compose logs goals-tracker-front
docker compose logs db

View last 100 lines:

docker compose logs --tail=100

Container Management

List running containers:

docker compose ps

Execute command in container:

docker compose exec goals-tracker-backend bash
docker compose exec db psql -U user -d goals_tracker_db

Restart a service:

docker compose restart goals-tracker-backend

Stop a specific service:

docker compose stop goals-tracker-backend

Start a stopped service:

docker compose start goals-tracker-backend

Volume Management

Database Persistence

Data is persisted in a Docker volume named pgdata.

List volumes:

docker volume ls

Inspect volume:

docker volume inspect goals-tracker_pgdata

Backup database:

docker compose exec db pg_dump -U user goals_tracker_db > backup.sql

Restore database:

docker compose exec -T db psql -U user goals_tracker_db < backup.sql

Networking

Service Communication

Services communicate using service names as hostnames:

  • Backend → Database: db:5432
  • Frontend → Backend: goals-tracker-backend:8080

Port Mapping

Service Internal Port External Port Access
Frontend 5173 or 80 5173 http://localhost:5173
Backend 8080 8080 http://localhost:8080
Database 5432 5432 localhost:5432

Environment Variables

Production Configuration

Create a .env file in the project root:

# Database
POSTGRES_USER=prod_user
POSTGRES_PASSWORD=strong_password_here
POSTGRES_DB=goals_tracker_prod

# Backend
JWT_SECRET=very-long-secret-key-for-production
JWT_EXPIRATION=86400000

# Frontend
VITE_API_URL=https://api.yourdomain.com

Use in docker-compose:

services:
  db:
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

Health Checks

Backend Health Check

curl http://localhost:8080/actuator/health

Expected response:

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP"
    }
  }
}

Database Health Check

docker compose exec db pg_isready -U user

Container Health Status

docker compose ps

Look for "healthy" status.

Troubleshooting

Container Won't Start

Check logs:

docker compose logs service-name

Check if port is already in use:

# Linux/Mac
lsof -i :8080
lsof -i :5173
lsof -i :5432

# Windows
netstat -ano | findstr :8080

Database Connection Issues

Verify database is running:

docker compose ps db

Test connection:

docker compose exec db psql -U user -d goals_tracker_db -c "SELECT 1"

Check backend environment variables:

docker compose exec goals-tracker-backend env | grep DATABASE

Backend Won't Connect to Database

Wait for database to be ready: - Add health checks to docker-compose.yml - Backend should wait for db service to be healthy

Check network:

docker network ls
docker network inspect goals-tracker_default

Out of Memory

Increase Docker memory: - Docker Desktop → Settings → Resources → Memory

Optimize Java heap:

ENTRYPOINT ["java", "-Xmx512m", "-Xms256m", "-jar", "app.jar"]

Slow Builds

Use build cache:

docker compose build --parallel

Prune unused images:

docker system prune -a

Performance Optimization

Build Cache

Layer order matters - frequently changing files should be copied last:

# Good - dependencies cached separately
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

# Bad - cache invalidated on any file change
COPY . .
RUN npm ci

Image Size

Use alpine images:

FROM node:18-alpine  # ~150MB
# vs
FROM node:18         # ~1GB

Multi-stage builds:

FROM maven:3.9 AS build
# ... build steps

FROM eclipse-temurin:17-jre-alpine
COPY --from=build /app/target/*.jar app.jar

Resource Limits

services:
  goals-tracker-backend:
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

Production Considerations

Security

  • Use secrets for sensitive data (not environment variables)
  • Don't expose database port in production
  • Use HTTPS with reverse proxy (nginx)
  • Regular security updates for base images

Monitoring

Add monitoring tools:

services:
  prometheus:
    image: prom/prometheus
    # ... configuration

  grafana:
    image: grafana/grafana
    # ... configuration

Scaling

Scale services horizontally:

docker compose up --scale goals-tracker-backend=3

Add load balancer (nginx, traefik) for distribution.

Next Steps