Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .dockerignore
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
272 changes: 272 additions & 0 deletions DOCKER_DEPLOYMENT.md
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 });
}
```
Comment on lines +118 to +127
Copy link

Copilot AI Apr 28, 2026

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/health and shows an example implementation at src/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.

Copilot uses AI. Check for mistakes.

### 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
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scaling example uses deploy.replicas, which is only honored by Docker Swarm; docker compose ignores deploy in non-swarm mode. To avoid misleading readers, either document swarm explicitly or switch the example to docker compose up --scale app=3 (and drop container_name in compose).

Copilot uses AI. Check for mistakes.

## 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/)
49 changes: 49 additions & 0 deletions Dockerfile
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
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The builder stage runs check-locales/check-i18n explicitly, but npm run build already triggers the prebuild script (which also runs these checks). This duplicates work during Docker builds; consider removing the explicit checks here or adjusting scripts so the validations run only once.

Suggested change
# 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 uses AI. Check for mistakes.
RUN npm run build -- --no-lint

# Stage 2: Production runtime
FROM node:20-alpine AS runtime

WORKDIR /app

# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001

# Copy built application from builder
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Set environment to production
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

USER nextjs

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
Copy link

Copilot AI Apr 28, 2026

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.

Suggested change
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));"

Copilot uses AI. Check for mistakes.

# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
18 changes: 18 additions & 0 deletions Dockerfile.dev
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"]
Loading
Loading