Skip to content

Commit f851bfe

Browse files
committed
feat(docker): add complete Docker Compose setup
- Add docker-compose.yml with MongoDB, backend, frontend, dashboard services - Add Dockerfile for backend (NestJS with pnpm workspaces) - Add Dockerfile for frontend (React + Vite + Nginx) - Add Dockerfile for dashboard (React + Vite + Nginx) - Add nginx.conf for SPA routing and security headers - Add .dockerignore for optimized builds - Add /health endpoint to backend for Docker healthchecks Services: - MongoDB: port 27018 (internal: 27017) - Backend API: port 1337 - Frontend demo: port 3000 - Dashboard admin: port 3001
1 parent f899acf commit f851bfe

8 files changed

Lines changed: 317 additions & 0 deletions

File tree

.dockerignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Dependencies
2+
node_modules
3+
**/node_modules
4+
5+
# Build outputs
6+
**/dist
7+
**/build
8+
**/.next
9+
10+
# Git
11+
.git
12+
.gitignore
13+
14+
# IDE
15+
.vscode
16+
.idea
17+
*.swp
18+
*.swo
19+
20+
# Logs
21+
*.log
22+
npm-debug.log*
23+
pnpm-debug.log*
24+
25+
# Environment files
26+
.env
27+
.env.local
28+
.env.*.local
29+
30+
# Test files
31+
**/*.test.ts
32+
**/*.spec.ts
33+
**/coverage
34+
35+
# Documentation
36+
docs/
37+
*.md
38+
!README.md
39+
40+
# Docker
41+
Dockerfile*
42+
docker-compose*.yml
43+
.dockerignore
44+
45+
# OS files
46+
.DS_Store
47+
Thumbs.db

docker-compose.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
services:
2+
# MongoDB Database
3+
mongodb:
4+
image: mongo:7
5+
container_name: tracker-mongodb
6+
restart: unless-stopped
7+
environment:
8+
MONGO_INITDB_ROOT_USERNAME: trackr
9+
MONGO_INITDB_ROOT_PASSWORD: trackr
10+
MONGO_INITDB_DATABASE: trackrdb
11+
volumes:
12+
- mongodb_data:/data/db
13+
ports:
14+
- "27018:27017" # Use 27018 externally to avoid conflicts
15+
networks:
16+
- tracker-network
17+
healthcheck:
18+
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
19+
interval: 10s
20+
timeout: 5s
21+
retries: 5
22+
start_period: 30s
23+
24+
# NestJS Backend API
25+
backend:
26+
build:
27+
context: .
28+
dockerfile: packages/backend/Dockerfile
29+
container_name: tracker-backend
30+
restart: unless-stopped
31+
environment:
32+
NODE_ENV: production
33+
PORT: 1337
34+
HOST: 0.0.0.0
35+
MONGO_URI: mongodb://trackr:trackr@mongodb:27017/trackrdb?authSource=admin
36+
ports:
37+
- "1337:1337"
38+
networks:
39+
- tracker-network
40+
depends_on:
41+
mongodb:
42+
condition: service_healthy
43+
healthcheck:
44+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:1337/health"]
45+
interval: 30s
46+
timeout: 10s
47+
retries: 3
48+
start_period: 40s
49+
50+
# React Frontend (Demo App)
51+
frontend:
52+
build:
53+
context: .
54+
dockerfile: packages/frontend/Dockerfile
55+
container_name: tracker-frontend
56+
restart: unless-stopped
57+
ports:
58+
- "3000:80"
59+
networks:
60+
- tracker-network
61+
depends_on:
62+
- backend
63+
64+
# React Dashboard (Admin Panel)
65+
dashboard:
66+
build:
67+
context: .
68+
dockerfile: packages/dashboard/Dockerfile
69+
args:
70+
VITE_API_URL: http://localhost:1337
71+
container_name: tracker-dashboard
72+
restart: unless-stopped
73+
ports:
74+
- "3001:80"
75+
networks:
76+
- tracker-network
77+
depends_on:
78+
- backend
79+
80+
volumes:
81+
mongodb_data:
82+
driver: local
83+
84+
networks:
85+
tracker-network:
86+
driver: bridge

packages/backend/Dockerfile

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Backend Dockerfile - NestJS API
2+
FROM node:20-alpine AS base
3+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
4+
WORKDIR /app
5+
6+
# Install dependencies
7+
FROM base AS deps
8+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
9+
COPY packages/core/package.json ./packages/core/
10+
COPY packages/backend/package.json ./packages/backend/
11+
RUN pnpm install --frozen-lockfile
12+
13+
# Build stage
14+
FROM base AS builder
15+
COPY --from=deps /app/node_modules ./node_modules
16+
COPY --from=deps /app/packages/core/node_modules ./packages/core/node_modules
17+
COPY --from=deps /app/packages/backend/node_modules ./packages/backend/node_modules
18+
COPY . .
19+
RUN pnpm --filter core build
20+
RUN pnpm --filter backend build
21+
22+
# Production stage
23+
FROM node:20-alpine AS runner
24+
RUN apk add --no-cache wget
25+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
26+
WORKDIR /app
27+
28+
ENV NODE_ENV=production
29+
ENV PORT=1337
30+
ENV HOST=0.0.0.0
31+
32+
# Copy package files and install production dependencies
33+
COPY --from=builder /app/package.json ./
34+
COPY --from=builder /app/pnpm-lock.yaml ./
35+
COPY --from=builder /app/pnpm-workspace.yaml ./
36+
COPY --from=builder /app/packages/backend/package.json ./packages/backend/
37+
COPY --from=builder /app/packages/core/package.json ./packages/core/
38+
COPY --from=builder /app/packages/core/dist ./packages/core/dist
39+
40+
# Install production dependencies only
41+
RUN pnpm install --prod --frozen-lockfile
42+
43+
# Copy built backend
44+
COPY --from=builder /app/packages/backend/dist ./packages/backend/dist
45+
46+
WORKDIR /app/packages/backend
47+
48+
EXPOSE 1337
49+
50+
CMD ["node", "dist/main.js"]

packages/backend/src/app.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,12 @@ export class AppController {
99
getHello(): string {
1010
return this.appService.getHello();
1111
}
12+
13+
@Get('health')
14+
getHealth(): { status: string; timestamp: string } {
15+
return {
16+
status: 'ok',
17+
timestamp: new Date().toISOString(),
18+
};
19+
}
1220
}

packages/dashboard/Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Dashboard Dockerfile - React Admin Panel
2+
FROM node:20-alpine AS base
3+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
4+
WORKDIR /app
5+
6+
# Install dependencies
7+
FROM base AS deps
8+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
9+
COPY packages/dashboard/package.json ./packages/dashboard/
10+
RUN pnpm install --frozen-lockfile
11+
12+
# Build stage
13+
FROM base AS builder
14+
ARG VITE_API_URL=http://localhost:1337
15+
ENV VITE_API_URL=$VITE_API_URL
16+
17+
COPY --from=deps /app/node_modules ./node_modules
18+
COPY --from=deps /app/packages/dashboard/node_modules ./packages/dashboard/node_modules
19+
COPY . .
20+
RUN pnpm --filter dashboard build
21+
22+
# Production stage - Nginx
23+
FROM nginx:alpine AS runner
24+
COPY --from=builder /app/packages/dashboard/dist /usr/share/nginx/html
25+
COPY packages/dashboard/nginx.conf /etc/nginx/conf.d/default.conf
26+
EXPOSE 80
27+
CMD ["nginx", "-g", "daemon off;"]

packages/dashboard/nginx.conf

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
server {
2+
listen 80;
3+
server_name localhost;
4+
root /usr/share/nginx/html;
5+
index index.html;
6+
7+
# Gzip compression
8+
gzip on;
9+
gzip_vary on;
10+
gzip_min_length 1024;
11+
gzip_proxied expired no-cache no-store private auth;
12+
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json;
13+
14+
# Security headers
15+
add_header X-Frame-Options "SAMEORIGIN" always;
16+
add_header X-Content-Type-Options "nosniff" always;
17+
add_header X-XSS-Protection "1; mode=block" always;
18+
19+
# SPA routing - redirect all requests to index.html
20+
location / {
21+
try_files $uri $uri/ /index.html;
22+
}
23+
24+
# Cache static assets
25+
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
26+
expires 1y;
27+
add_header Cache-Control "public, immutable";
28+
}
29+
30+
# Health check endpoint
31+
location /health {
32+
access_log off;
33+
return 200 "OK";
34+
add_header Content-Type text/plain;
35+
}
36+
}

packages/frontend/Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Frontend Dockerfile - React Demo App
2+
FROM node:20-alpine AS base
3+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
4+
WORKDIR /app
5+
6+
# Install dependencies
7+
FROM base AS deps
8+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
9+
COPY packages/core/package.json ./packages/core/
10+
COPY packages/frontend/package.json ./packages/frontend/
11+
RUN pnpm install --frozen-lockfile
12+
13+
# Build stage
14+
FROM base AS builder
15+
COPY --from=deps /app/node_modules ./node_modules
16+
COPY --from=deps /app/packages/core/node_modules ./packages/core/node_modules
17+
COPY --from=deps /app/packages/frontend/node_modules ./packages/frontend/node_modules
18+
COPY . .
19+
RUN pnpm --filter core build
20+
RUN pnpm --filter frontend build
21+
22+
# Production stage - Nginx
23+
FROM nginx:alpine AS runner
24+
COPY --from=builder /app/packages/frontend/dist /usr/share/nginx/html
25+
COPY packages/frontend/nginx.conf /etc/nginx/conf.d/default.conf
26+
EXPOSE 80
27+
CMD ["nginx", "-g", "daemon off;"]

packages/frontend/nginx.conf

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
server {
2+
listen 80;
3+
server_name localhost;
4+
root /usr/share/nginx/html;
5+
index index.html;
6+
7+
# Gzip compression
8+
gzip on;
9+
gzip_vary on;
10+
gzip_min_length 1024;
11+
gzip_proxied expired no-cache no-store private auth;
12+
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json;
13+
14+
# Security headers
15+
add_header X-Frame-Options "SAMEORIGIN" always;
16+
add_header X-Content-Type-Options "nosniff" always;
17+
add_header X-XSS-Protection "1; mode=block" always;
18+
19+
# SPA routing - redirect all requests to index.html
20+
location / {
21+
try_files $uri $uri/ /index.html;
22+
}
23+
24+
# Cache static assets
25+
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
26+
expires 1y;
27+
add_header Cache-Control "public, immutable";
28+
}
29+
30+
# Health check endpoint
31+
location /health {
32+
access_log off;
33+
return 200 "OK";
34+
add_header Content-Type text/plain;
35+
}
36+
}

0 commit comments

Comments
 (0)