Docker DevOps Skill for Happy Camper Planner¶
This skill provides specialized guidance for Docker containerization, development environment setup, and DevOps workflows in the Happy Camper Planner collaborative camping trip planning application.
When to Use This Skill¶
Use this skill when you need to: - Configure or troubleshoot Docker Compose services - Optimize Dockerfiles for .NET API and React webapp - Set up development containers and VS Code integration - Create production-ready container configurations - Implement CI/CD pipelines with Docker - Debug container networking and connectivity issues - Configure database and cache containers (PostgreSQL, Redis) - Set up health checks and monitoring - Optimize container build times and image sizes - Handle environment-specific configurations
Technology Context¶
Container Stack¶
- Docker Compose for multi-service orchestration
- PostgreSQL 16 Alpine for primary database
- Redis 7 Alpine for caching and sessions
- .NET 8 SDK containers for API development
- Node.js 20 containers for React webapp
- Multi-stage builds for production optimization
Development Environment¶
# Current docker-compose.yaml structure
services:
db: postgres:16-alpine (port 5432)
cache: redis:7-alpine (port 6379)
api: .NET 8 development container (port 5000)
webapp: React/Vite development container (port 3000)
Connection Configuration¶
# Development database connection
Host=db;Database=happy_camper_db;Username=camper_admin;Password=dev_password_123
# Redis connection
host=cache:6379
Docker Compose Management¶
Complete Development Configuration¶
version: '3.8'
services:
# PostgreSQL Database
db:
image: postgres:16-alpine
container_name: happy-camper-db
restart: always
environment:
POSTGRES_USER: camper_admin
POSTGRES_PASSWORD: dev_password_123
POSTGRES_DB: happy_camper_db
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
networks:
- camper-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U camper_admin -d happy_camper_db"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
# Redis Cache
cache:
image: redis:7-alpine
container_name: happy-camper-cache
restart: always
command: redis-server --appendonly yes --requirepass dev_redis_password
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- camper-network
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 30s
timeout: 10s
retries: 5
start_period: 10s
# .NET API Service
api:
build:
context: ./src/api
dockerfile: Dockerfile.dev
target: development
container_name: happy-camper-api
restart: unless-stopped
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080
- ConnectionStrings__DefaultConnection=Host=db;Database=happy_camper_db;Username=camper_admin;Password=dev_password_123
- ConnectionStrings__Redis=cache:6379,password=dev_redis_password
- Firebase__ProjectId=${FIREBASE_PROJECT_ID}
- Firebase__ServiceAccountPath=/app/secrets/firebase-service-account.json
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
ports:
- "5000:8080"
volumes:
- ./src/api:/app
- ./secrets:/app/secrets:ro
- api_packages:/root/.nuget/packages
networks:
- camper-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# React WebApp
webapp:
build:
context: ./src/webapp
dockerfile: Dockerfile.dev
target: development
container_name: happy-camper-webapp
restart: unless-stopped
environment:
- NODE_ENV=development
- VITE_API_URL=http://localhost:5000
- VITE_FIREBASE_API_KEY=${FIREBASE_API_KEY}
- VITE_FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN}
- VITE_FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID}
depends_on:
- api
ports:
- "3000:3000"
volumes:
- ./src/webapp:/app
- webapp_node_modules:/app/node_modules
networks:
- camper-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# React Native Mobile (Development)
mobile:
build:
context: ./src/mobile
dockerfile: Dockerfile.dev
container_name: happy-camper-mobile
restart: unless-stopped
environment:
- EXPO_DEVTOOLS_LISTEN_ADDRESS=0.0.0.0
- API_URL=http://api:8080
depends_on:
- api
ports:
- "19000:19000" # Expo DevTools
- "19001:19001" # Expo Metro bundler
volumes:
- ./src/mobile:/app
- mobile_node_modules:/app/node_modules
networks:
- camper-network
volumes:
postgres_data:
driver: local
redis_data:
driver: local
api_packages:
driver: local
webapp_node_modules:
driver: local
mobile_node_modules:
driver: local
networks:
camper-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
Environment-Specific Overrides¶
# docker-compose.override.yml (development)
version: '3.8'
services:
api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- Logging__LogLevel__Default=Debug
volumes:
- ./src/api:/app
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
webapp:
environment:
- NODE_ENV=development
command: npm run dev -- --host 0.0.0.0
# docker-compose.prod.yml (production)
version: '3.8'
services:
db:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
secrets:
- postgres_password
api:
build:
target: production
environment:
- ASPNETCORE_ENVIRONMENT=Production
deploy:
replicas: 2
resources:
limits:
memory: 512M
reservations:
memory: 256M
secrets:
postgres_password:
external: true
Dockerfile Optimization¶
.NET API Multi-Stage Dockerfile¶
# src/api/Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
# Copy project files and restore dependencies
COPY ["*.csproj", "./"]
RUN dotnet restore --runtime alpine-x64
# Copy source code and build
COPY . .
RUN dotnet publish -c Release -o /app/publish \
--runtime alpine-x64 \
--self-contained false \
--no-restore
# Development stage
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS development
WORKDIR /app
RUN apk add --no-cache curl
COPY . .
RUN dotnet restore
ENTRYPOINT ["dotnet", "watch", "run", "--urls", "http://0.0.0.0:8080"]
# Production stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS production
WORKDIR /app
# Install security updates and required packages
RUN apk upgrade --no-cache && \
apk add --no-cache curl tzdata && \
adduser --disabled-password --home /app --gecos '' appuser && \
chown -R appuser /app
# Copy published application
COPY --from=build /app/publish .
# Switch to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
EXPOSE 8080
ENTRYPOINT ["dotnet", "HappyCamper.Api.dll"]
React WebApp Multi-Stage Dockerfile¶
# src/webapp/Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
RUN apk add --no-cache libc6-compat curl
# Development stage
FROM base AS development
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# Build stage
FROM base AS build
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .
RUN npm run build
# Production stage
FROM nginx:1.24-alpine AS production
RUN apk upgrade --no-cache
# Copy built application
COPY --from=build /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Add health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
React Native Dockerfile¶
# src/mobile/Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
RUN apk add --no-cache git python3 make g++
# Install Expo CLI
RUN npm install -g @expo/cli
# Copy package files
COPY package*.json ./
RUN npm ci
# Copy source code
COPY . .
EXPOSE 19000 19001 19002
CMD ["npx", "expo", "start", "--tunnel"]
Development Environment Setup¶
VS Code DevContainer Configuration¶
// .devcontainer/api/devcontainer.json
{
"name": "Happy Camper API",
"dockerComposeFile": ["../../docker-compose.yml", "docker-compose.devcontainer.yml"],
"service": "api-dev",
"workspaceFolder": "/app",
"shutdownAction": "stopCompose",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"ms-vscode.vscode-json",
"bradlc.vscode-tailwindcss",
"esbenp.prettier-vscode"
],
"settings": {
"dotnet.defaultSolution": "HappyCamper.sln"
}
}
},
"forwardPorts": [5000, 5432, 6379],
"portsAttributes": {
"5000": {
"label": "API",
"onAutoForward": "notify"
}
},
"postCreateCommand": "dotnet restore",
"remoteUser": "root"
}
# .devcontainer/api/docker-compose.devcontainer.yml
version: '3.8'
services:
api-dev:
build:
context: ../../src/api
dockerfile: Dockerfile.dev
volumes:
- ../../:/workspace:cached
- api-extensions:/root/.vscode-server/extensions
command: sleep infinity
depends_on:
- db
- cache
volumes:
api-extensions:
Docker Commands and Scripts¶
Development Commands
#!/bin/bash
# scripts/dev.sh
echo "Starting Happy Camper development environment..."
# Pull latest images
docker-compose pull
# Build and start services
docker-compose up -d --build
# Wait for services to be healthy
echo "Waiting for services to be ready..."
docker-compose exec db pg_isready -U camper_admin -d happy_camper_db
docker-compose exec cache redis-cli ping
# Run database migrations
echo "Running database migrations..."
docker-compose exec api dotnet ef database update
# Show service status
docker-compose ps
echo "Environment ready! Services available at:"
echo " - API: http://localhost:5000"
echo " - WebApp: http://localhost:3000"
echo " - Database: localhost:5432"
echo " - Redis: localhost:6379"
Production Deployment Script
#!/bin/bash
# scripts/deploy.sh
set -e
ENVIRONMENT=${1:-staging}
IMAGE_TAG=${2:-latest}
echo "Deploying Happy Camper to $ENVIRONMENT..."
# Build production images
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build
# Tag images for registry
docker tag happy-camper-api:latest gcr.io/happy-camper-project/api:$IMAGE_TAG
docker tag happy-camper-webapp:latest gcr.io/happy-camper-project/webapp:$IMAGE_TAG
# Push to registry
docker push gcr.io/happy-camper-project/api:$IMAGE_TAG
docker push gcr.io/happy-camper-project/webapp:$IMAGE_TAG
# Deploy to environment
if [ "$ENVIRONMENT" = "production" ]; then
echo "Deploying to production..."
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
else
echo "Deploying to staging..."
docker-compose -f docker-compose.yml -f docker-compose.staging.yml up -d
fi
echo "Deployment complete!"
Database Management¶
PostgreSQL Container Configuration¶
# Database initialization
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_MULTIPLE_DATABASES: happy_camper_db,happy_camper_test
POSTGRES_USER: camper_admin
POSTGRES_PASSWORD: dev_password_123
volumes:
- ./scripts/create-multiple-postgresql-databases.sh:/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh:ro
- ./scripts/init-extensions.sql:/docker-entrypoint-initdb.d/init-extensions.sql:ro
- postgres_data:/var/lib/postgresql/data
-- scripts/init-extensions.sql
-- Enable required PostgreSQL extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Create additional databases for testing
SELECT 'CREATE DATABASE happy_camper_test'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'happy_camper_test')\gexec
Database Migration and Seeding¶
# Database management commands
docker-compose exec api dotnet ef migrations add InitialCreate
docker-compose exec api dotnet ef database update
docker-compose exec api dotnet run --seed-data
# Backup and restore
docker-compose exec db pg_dump -U camper_admin happy_camper_db > backup.sql
docker-compose exec -T db psql -U camper_admin happy_camper_db < backup.sql
CI/CD Pipeline Configuration¶
GitHub Actions Workflow¶
# .github/workflows/ci-cd.yml
name: Happy Camper CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: gcr.io
PROJECT_ID: happy-camper-project
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: camper_admin
POSTGRES_PASSWORD: test_password
POSTGRES_DB: happy_camper_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: src/webapp/package-lock.json
- name: Restore .NET dependencies
run: dotnet restore src/api
- name: Install Node dependencies
run: npm ci
working-directory: src/webapp
- name: Run .NET tests
env:
ConnectionStrings__DefaultConnection: Host=localhost;Database=happy_camper_test;Username=camper_admin;Password=test_password
ConnectionStrings__Redis: localhost:6379
run: |
dotnet test src/api --no-restore --verbosity normal \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage
- name: Run React tests
run: npm test -- --coverage --watchAll=false
working-directory: src/webapp
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
directory: ./coverage
flags: unittests
build-and-deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
- name: Configure Docker for GCR
run: gcloud auth configure-docker gcr.io
- name: Build Docker images
run: |
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build
docker tag happy-camper-api:latest $REGISTRY/$PROJECT_ID/api:$GITHUB_SHA
docker tag happy-camper-webapp:latest $REGISTRY/$PROJECT_ID/webapp:$GITHUB_SHA
- name: Push to Container Registry
run: |
docker push $REGISTRY/$PROJECT_ID/api:$GITHUB_SHA
docker push $REGISTRY/$PROJECT_ID/webapp:$GITHUB_SHA
- name: Deploy to Cloud Run
run: |
gcloud run deploy happy-camper-api \
--image $REGISTRY/$PROJECT_ID/api:$GITHUB_SHA \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars="ASPNETCORE_ENVIRONMENT=Production"
gcloud run deploy happy-camper-webapp \
--image $REGISTRY/$PROJECT_ID/webapp:$GITHUB_SHA \
--platform managed \
--region us-central1 \
--allow-unauthenticated
Monitoring and Health Checks¶
Application Health Endpoints¶
// API Health Check Implementation
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// Register health checks
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString, tags: new[] { "ready" })
.AddRedis(redisConnectionString, tags: new[] { "ready" })
.AddUrlGroup(new Uri("https://firebase.googleapis.com"), name: "firebase", tags: new[] { "external" });
Container Monitoring with Prometheus¶
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: happy-camper-prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
networks:
- camper-network
grafana:
image: grafana/grafana:latest
container_name: happy-camper-grafana
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
networks:
- camper-network
volumes:
prometheus_data:
grafana_data:
Security and Best Practices¶
Security Hardening¶
# Security-hardened base image
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS production
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Install security updates
RUN apk upgrade --no-cache && \
apk add --no-cache curl ca-certificates && \
rm -rf /var/cache/apk/*
# Set proper ownership
WORKDIR /app
COPY --from=build --chown=appuser:appgroup /app/publish .
# Use non-root user
USER appuser
# Remove unnecessary capabilities
USER 1001:1001
Secret Management¶
# Use Docker secrets for production
version: '3.8'
services:
api:
secrets:
- database_password
- firebase_service_account
- jwt_signing_key
environment:
- ConnectionStrings__DefaultConnection_FILE=/run/secrets/database_password
- Firebase__ServiceAccountPath=/run/secrets/firebase_service_account
secrets:
database_password:
external: true
firebase_service_account:
external: true
jwt_signing_key:
external: true
Troubleshooting Guide¶
Common Docker Issues¶
Issue: Port Already in Use
# Find and kill process using port
lsof -ti:5000 | xargs kill -9
# Or use different port in docker-compose.yml
ports:
- "5001:8080" # Changed from 5000:8080
Issue: Database Connection Failed
# Check database container logs
docker-compose logs db
# Connect to database directly
docker-compose exec db psql -U camper_admin -d happy_camper_db
# Reset database volume
docker-compose down -v
docker-compose up db
Issue: Out of Disk Space
# Clean up Docker resources
docker system prune -a --volumes
# Remove unused images
docker image prune -a
# Check disk usage
docker system df
Issue: Container Won't Start
# Check container logs
docker-compose logs api
# Run container in interactive mode for debugging
docker-compose run --rm api sh
# Check container resource usage
docker stats
Performance Optimization¶
Build Cache Optimization
# Layer caching for faster builds
COPY package*.json ./
RUN npm ci --only=production
# Copy source files last (changes more frequently)
COPY . .
Multi-Stage Build Cleanup
# Remove development dependencies in production
FROM node:20-alpine AS deps
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine AS build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
# deps layer not included in final image
Environment Configuration¶
Development Environment File¶
# .env.development
COMPOSE_PROJECT_NAME=happy-camper-dev
POSTGRES_PASSWORD=dev_password_123
REDIS_PASSWORD=dev_redis_password
FIREBASE_PROJECT_ID=happy-camper-dev
API_BASE_URL=http://localhost:5000
WEBAPP_PORT=3000
API_PORT=5000
Production Environment¶
# .env.production
COMPOSE_PROJECT_NAME=happy-camper-prod
POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
REDIS_PASSWORD_FILE=/run/secrets/redis_password
FIREBASE_PROJECT_ID=happy-camper-production
API_BASE_URL=https://api.happycamper.app
Remember: This skill automatically activates when working on Docker, containerization, or DevOps tasks. Always prioritize security, performance, and maintainability in your container configurations.