chore: standardize Docker setup for dev and production#279
Conversation
Add multi-stage production Docker build, dedicated dev Dockerfile, compose configs, and deployment docs. Enable Next.js standalone output for optimized runtime images. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@TS-mfon Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
There was a problem hiding this comment.
Pull request overview
Standardizes Docker workflows for development and production, introducing multi-stage builds and documentation, and enabling Next.js standalone output to produce smaller/cleaner runtime images.
Changes:
- Enable Next.js
output: 'standalone'to support minimal production runtime images. - Add production multi-stage
Dockerfile, devDockerfile.dev, and split Compose configs for prod vs dev. - Add
.dockerignoreand a deployment guide describing build/run/deploy flows.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| next.config.ts | Enables standalone output required by the runtime image approach. |
| docker-compose.yml | Adds production Compose service definition, runtime build target, and healthcheck. |
| docker-compose.dev.yml | Adds dev overrides for hot-reload workflow using Dockerfile.dev. |
| Dockerfile.dev | Defines a dev image with dependencies for next dev. |
| Dockerfile | Adds multi-stage build with a minimal runtime stage using standalone output. |
| DOCKER_DEPLOYMENT.md | Documents dev/prod Docker usage, healthchecks, and CI/CD suggestions. |
| .dockerignore | Reduces build context size and avoids copying local artifacts into images. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| context: . | ||
| dockerfile: Dockerfile | ||
| target: runtime | ||
| container_name: teachlink-app |
There was a problem hiding this comment.
container_name prevents scaling the service (e.g., docker compose up --scale app=3) and can cause name collisions across environments. Consider removing container_name and relying on Compose’s autogenerated names, especially since the docs discuss running multiple instances.
| container_name: teachlink-app |
|
|
||
| volumes: | ||
| logs: |
There was a problem hiding this comment.
A named volume logs: is declared but the service mounts a bind mount (./logs:/app/logs) instead, so the logs volume is unused. Either remove the unused named volume declaration or switch the service to use logs:/app/logs (and update the docs accordingly).
| volumes: | |
| logs: |
| # Run build-time validations | ||
| RUN npm run check-locales && npm run check-i18n | ||
|
|
||
| # Build the Next.js application (lint is handled separately in CI) |
There was a problem hiding this comment.
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.
| # 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. |
| 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)})" |
There was a problem hiding this comment.
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));" |
| 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 }); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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 | ||
| ``` |
There was a problem hiding this comment.
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).
| healthcheck: | ||
| test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"] | ||
| interval: 30s | ||
| timeout: 3s | ||
| retries: 3 | ||
| start_period: 5s |
There was a problem hiding this comment.
The compose healthcheck calls http://localhost:3000/api/health, but there is no /api/health route in src/app/api (and no other match in the repo). As-is, containers will be reported unhealthy. Either add the endpoint (e.g., src/app/api/health/route.ts) or change the healthcheck to an existing path.
closes #239
Add multi-stage production Docker build, dedicated dev Dockerfile, compose configs, and deployment docs. Enable Next.js standalone output for optimized runtime images.
Summary
Related Issue
Closes #
Type of change
Screenshots / Recording (if UI)
Testing
npm run type-checknpm run lintnpm run testnpm run buildQuality gate checklist
Closes #<issue-number>