-
Notifications
You must be signed in to change notification settings - Fork 170
chore: standardize Docker setup for dev and production #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # Dependencies | ||
| node_modules | ||
| npm-debug.log | ||
| yarn-error.log | ||
|
|
||
| # Build outputs | ||
| .next | ||
| dist | ||
| build | ||
|
|
||
| # Development | ||
| .git | ||
| .gitignore | ||
| .eslintignore | ||
| .prettierignore | ||
| .husky | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
|
|
||
| # IDE and editors | ||
| .vscode | ||
| .idea | ||
| *.swp | ||
| *.swo | ||
| *~ | ||
| .DS_Store | ||
|
|
||
| # Testing | ||
| coverage | ||
| .vitest | ||
| test-output*.txt | ||
|
|
||
| # Documentation | ||
| README.md | ||
| CONTRIBUTING.md | ||
| docs | ||
| screenshots | ||
|
|
||
| # CI/CD | ||
| .github | ||
| .gitlab-ci.yml | ||
|
|
||
| # Temporary files | ||
| *.tmp | ||
| *.temp | ||
| .cache | ||
|
|
||
| # Lock files (optional - include for reproducibility) | ||
| # package-lock.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,272 @@ | ||
| # Docker Deployment Guide | ||
|
|
||
| This document provides comprehensive instructions for building, running, and deploying the TeachLink application using Docker. | ||
|
|
||
| ## Architecture | ||
|
|
||
| The Docker setup uses **multi-stage builds** to optimize: | ||
| - **Build Stage**: Compiles Next.js and validates i18n configuration | ||
| - **Runtime Stage**: Lean production image with minimal dependencies | ||
|
|
||
| ### Image Optimization | ||
| - Base image: `node:20-alpine` (~150MB) | ||
| - Production image size: ~250-300MB (after build) | ||
| - Development image: Includes dev dependencies for fast iteration | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Docker 20.10+ | ||
| - Docker Compose 2.0+ | ||
| - Git | ||
|
|
||
| ## Development Setup | ||
|
|
||
| ### Quick Start (Hot Reload) | ||
|
|
||
| ```bash | ||
| # Start development environment with hot-reload | ||
| docker-compose -f docker-compose.yml -f docker-compose.dev.yml up | ||
|
|
||
| # Access the app at http://localhost:3000 | ||
| ``` | ||
|
|
||
| ### Development Features | ||
|
|
||
| - **Volume Mounts**: Source code changes immediately reflected | ||
| - **Hot Reload**: Next.js dev server with Turbopack | ||
| - **Network**: Isolated `teachlink-network` for scalability | ||
| - **Logs**: Available at `./logs` directory | ||
|
|
||
| ### Rebuild Containers | ||
|
|
||
| ```bash | ||
| # Full rebuild | ||
| docker-compose -f docker-compose.yml -f docker-compose.dev.yml up --build | ||
|
|
||
| # Remove old images | ||
| docker-compose down | ||
| docker system prune -a | ||
| ``` | ||
|
|
||
| ## Production Deployment | ||
|
|
||
| ### Build Production Image | ||
|
|
||
| ```bash | ||
| # Build the production image | ||
| docker build -t teachlink:latest . | ||
|
|
||
| # Tag for registry (example: DockerHub) | ||
| docker tag teachlink:latest yourusername/teachlink:latest | ||
| docker tag teachlink:latest yourusername/teachlink:1.0.0 | ||
| ``` | ||
|
|
||
| ### Run Production Container | ||
|
|
||
| #### Using docker-compose (Recommended) | ||
|
|
||
| ```bash | ||
| # Start production services | ||
| docker-compose up -d | ||
|
|
||
| # View logs | ||
| docker-compose logs -f app | ||
|
|
||
| # Stop services | ||
| docker-compose down | ||
| ``` | ||
|
|
||
| #### Using docker directly | ||
|
|
||
| ```bash | ||
| docker run -d \ | ||
| --name teachlink-app \ | ||
| -p 3000:3000 \ | ||
| -e NODE_ENV=production \ | ||
| -e NEXT_TELEMETRY_DISABLED=1 \ | ||
| --restart unless-stopped \ | ||
| --health-cmd='node -e "require('"'"'http'"'"').get('"'"'http://localhost:3000/api/health'"'"', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"' \ | ||
| --health-interval=30s \ | ||
| --health-timeout=3s \ | ||
| --health-start-period=5s \ | ||
| --health-retries=3 \ | ||
| teachlink:latest | ||
| ``` | ||
|
|
||
| ## Environment Configuration | ||
|
|
||
| ### Production Variables | ||
|
|
||
| ```env | ||
| NODE_ENV=production | ||
| NEXT_TELEMETRY_DISABLED=1 | ||
| ``` | ||
|
|
||
| Add other required environment variables in `docker-compose.yml` or pass via `-e` flag. | ||
|
|
||
| ### Development Variables | ||
|
|
||
| ```env | ||
| NODE_ENV=development | ||
| NEXT_TELEMETRY_DISABLED=1 | ||
| ``` | ||
|
|
||
| ## Health Checks | ||
|
|
||
| ### Endpoint | ||
|
|
||
| The container includes a built-in health check that calls `/api/health`. | ||
|
|
||
| Ensure your app has this endpoint: | ||
|
|
||
| ```typescript | ||
| // src/app/api/health/route.ts | ||
| export async function GET() { | ||
| return Response.json({ status: 'healthy' }, { status: 200 }); | ||
| } | ||
| ``` | ||
|
|
||
| ### Manual Health Check | ||
|
|
||
| ```bash | ||
| docker exec teachlink-app node -e "require('http').get('http://localhost:3000/api/health', (r) => console.log(r.statusCode))" | ||
| ``` | ||
|
|
||
| ## Security Best Practices | ||
|
|
||
| ### Image Scanning | ||
|
|
||
| ```bash | ||
| # Scan for vulnerabilities (requires Trivy) | ||
| trivy image teachlink:latest | ||
| ``` | ||
|
|
||
| ### Non-Root User | ||
|
|
||
| The production image runs as `nextjs` (UID 1001) for security. | ||
|
|
||
| ### Network Isolation | ||
|
|
||
| - Services run on `teachlink-network` | ||
| - Only expose necessary ports | ||
| - Use environment variables for sensitive data | ||
|
|
||
| ## Scaling | ||
|
|
||
| ### Multiple Instances with Load Balancing | ||
|
|
||
| ```yaml | ||
| # docker-compose.yml example | ||
| services: | ||
| nginx: | ||
| image: nginx:alpine | ||
| ports: | ||
| - "80:80" | ||
| volumes: | ||
| - ./nginx.conf:/etc/nginx/nginx.conf:ro | ||
| depends_on: | ||
| - app | ||
|
|
||
| app: | ||
| build: . | ||
| deploy: | ||
| replicas: 3 | ||
| ``` | ||
|
Comment on lines
+158
to
+174
|
||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Container Won't Start | ||
|
|
||
| ```bash | ||
| # View logs | ||
| docker-compose logs app | ||
|
|
||
| # Inspect image | ||
| docker inspect teachlink:latest | ||
|
|
||
| # Run interactive shell | ||
| docker run -it --entrypoint /bin/sh teachlink:latest | ||
| ``` | ||
|
|
||
| ### High Memory Usage | ||
|
|
||
| ```bash | ||
| # Check memory consumption | ||
| docker stats teachlink-app | ||
|
|
||
| # Limit memory in docker-compose.yml | ||
| deploy: | ||
| resources: | ||
| limits: | ||
| memory: 512M | ||
| reservations: | ||
| memory: 256M | ||
| ``` | ||
|
|
||
| ### Port Already in Use | ||
|
|
||
| ```bash | ||
| # Find process using port 3000 | ||
| lsof -i :3000 | ||
|
|
||
| # Change port in docker-compose.yml | ||
| ports: | ||
| - "3001:3000" | ||
| ``` | ||
|
|
||
| ## CI/CD Integration | ||
|
|
||
| ### GitHub Actions Example | ||
|
|
||
| ```yaml | ||
| name: Build and Push Docker Image | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v2 | ||
|
|
||
| - name: Login to DockerHub | ||
| uses: docker/login-action@v2 | ||
| with: | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
|
||
| - name: Build and push | ||
| uses: docker/build-push-action@v4 | ||
| with: | ||
| context: . | ||
| push: true | ||
| tags: yourusername/teachlink:latest | ||
| cache-from: type=registry,ref=yourusername/teachlink:buildcache | ||
| cache-to: type=registry,ref=yourusername/teachlink:buildcache,mode=max | ||
| ``` | ||
|
|
||
| ## Cleanup | ||
|
|
||
| ```bash | ||
| # Stop all containers | ||
| docker-compose down | ||
|
|
||
| # Remove unused images | ||
| docker image prune | ||
|
|
||
| # Remove all Docker resources | ||
| docker system prune -a --volumes | ||
| ``` | ||
|
|
||
| ## Reference | ||
|
|
||
| - [Next.js Docker Documentation](https://nextjs.org/docs/deployment/docker) | ||
| - [Docker Best Practices](https://docs.docker.com/develop/dev-best-practices/) | ||
| - [Docker Compose Reference](https://docs.docker.com/compose/compose-file/) | ||
| - [Alpine Linux Benefits](https://www.alpinelinux.org/) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||
| # Multi-stage build for optimal image size and production performance | ||||||||||||||||
| # Stage 1: Build stage | ||||||||||||||||
| FROM node:20-alpine AS builder | ||||||||||||||||
|
|
||||||||||||||||
| WORKDIR /app | ||||||||||||||||
|
|
||||||||||||||||
| # Install ALL dependencies including dev dependencies (needed for build) | ||||||||||||||||
| COPY package.json package-lock.json ./ | ||||||||||||||||
| RUN npm ci --ignore-scripts && npm cache clean --force | ||||||||||||||||
|
|
||||||||||||||||
| # Copy source code | ||||||||||||||||
| COPY . . | ||||||||||||||||
|
|
||||||||||||||||
| # Run build-time validations | ||||||||||||||||
| RUN npm run check-locales && npm run check-i18n | ||||||||||||||||
|
|
||||||||||||||||
| # Build the Next.js application (lint is handled separately in CI) | ||||||||||||||||
|
Comment on lines
+14
to
+17
|
||||||||||||||||
| # Run build-time validations | |
| RUN npm run check-locales && npm run check-i18n | |
| # Build the Next.js application (lint is handled separately in CI) | |
| # Build the Next.js application (lint is handled separately in CI) | |
| # `npm run build` also runs the npm `prebuild` lifecycle, which performs | |
| # the build-time validations without duplicating work in the Docker build. |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The image-level HEALTHCHECK hits /api/health, but the repository doesn’t currently include that route under src/app/api. This will cause the container to be marked unhealthy in production. Either add the endpoint or update the healthcheck to a route that exists.
| CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" | |
| CMD node -e "const net = require('net'); const socket = net.connect({ host: '127.0.0.1', port: 3000 }, () => { socket.end(); process.exit(0); }); socket.setTimeout(2000); socket.on('timeout', () => { socket.destroy(); process.exit(1); }); socket.on('error', () => process.exit(1));" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Development Dockerfile with hot-reload support | ||
| FROM node:20-alpine AS development | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Install development dependencies | ||
| COPY package.json package-lock.json ./ | ||
| RUN npm ci | ||
|
|
||
| # Copy source code | ||
| COPY . . | ||
|
|
||
| ENV NODE_ENV=development | ||
| ENV NEXT_TELEMETRY_DISABLED=1 | ||
|
|
||
| EXPOSE 3000 | ||
|
|
||
| CMD ["npm", "run", "dev"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This health check section references
/api/healthand shows an example implementation atsrc/app/api/health/route.ts, but that route doesn’t exist in the current codebase. Since the Dockerfile/compose healthchecks depend on it, consider adding the route as part of this PR or documenting the exact existing endpoint to use instead.