Skip to content

Commit f7eca1d

Browse files
committed
feat: Refactor Docker setup and add health check endpoints for backend services
1 parent 10bb27f commit f7eca1d

File tree

7 files changed

+244
-36
lines changed

7 files changed

+244
-36
lines changed

client-test/.dockerignore

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,48 @@
1-
node_modules
1+
const ignorePatterns = [
2+
"node_modules",
3+
"npm-debug.log*",
4+
"yarn-debug.log*",
5+
"yarn-error.log*",
6+
".npm",
7+
".yarn",
8+
".env*",
9+
"!.env.example",
10+
".git",
11+
".gitignore",
12+
"README.md",
13+
".DS_Store",
14+
".vscode",
15+
".idea",
16+
"*.swp",
17+
"*.swo",
18+
"*~",
19+
"logs",
20+
"*.log",
21+
"coverage",
22+
".nyc_output",
23+
".cache",
24+
"dist",
25+
"build",
26+
".tmp",
27+
"temp",
28+
"tmp",
29+
".DS_Store",
30+
"Thumbs.db",
31+
"*.tmp",
32+
"*.temp",
33+
"Dockerfile*",
34+
"docker-compose*.yml",
35+
".dockerignore",
36+
"*.md",
37+
"*.config.*",
38+
"*.ts",
39+
"*.tsx",
40+
"*.js",
41+
"*.jsx",
42+
"*.test.*",
43+
"*.spec.*",
44+
"*.stories.*",
45+
".storybook",
46+
"coverage",
47+
".nyc_output"
48+
];

client-test/Dockerfile

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,26 @@ COPY . .
1616
FROM base AS build
1717
RUN npm run build
1818

19-
FROM node:24-alpine AS production
19+
FROM nginx:alpine AS production
2020
WORKDIR /app
2121

22-
COPY package*.json ./
23-
RUN npm i --omit=dev
24-
COPY --from=build /app/dist ./dist
22+
COPY --from=build /app/dist /usr/share/nginx/html
23+
24+
# Copy custom nginx config for SPA routing
25+
RUN echo 'server {\
26+
listen 80;\
27+
server_name localhost;\
28+
location / {\
29+
root /usr/share/nginx/html;\
30+
index index.html index.htm;\
31+
try_files $uri $uri/ /index.html;\
32+
}\
33+
location /api {\
34+
proxy_pass http://backend:5000;\
35+
proxy_set_header Host $host;\
36+
proxy_set_header X-Real-IP $remote_addr;\
37+
}\
38+
}' > /etc/nginx/conf.d/default.conf
2539

26-
EXPOSE 5173
27-
CMD ["npm", "run", "dev", "--", "--host"]
40+
EXPOSE 80
41+
CMD ["nginx", "-g", "daemon off;"]

docker-compose.yml

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,84 @@
11
services:
22

3-
frontend:
4-
build:
5-
context: ./client-test
6-
dockerfile: Dockerfile
3+
mongo:
4+
image: mongo:7-jammy
5+
container_name: codequest-mongo
6+
restart: unless-stopped
77
ports:
8-
- "5173:5173"
9-
depends_on:
10-
- backend
8+
- "27017:27017"
9+
environment:
10+
MONGO_INITDB_ROOT_USERNAME: admin
11+
MONGO_INITDB_ROOT_PASSWORD: password
12+
volumes:
13+
- mongo_data:/data/db
14+
- ./mongo-init:/docker-entrypoint-initdb.d
15+
networks:
16+
- app_network
17+
healthcheck:
18+
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
19+
interval: 10s
20+
timeout: 5s
21+
retries: 3
22+
23+
redis:
24+
image: redis:7-alpine
25+
container_name: codequest-redis
26+
restart: unless-stopped
27+
ports:
28+
- "6379:6379"
29+
volumes:
30+
- redis_data:/data
1131
networks:
1232
- app_network
33+
healthcheck:
34+
test: ["CMD", "redis-cli", "ping"]
35+
interval: 10s
36+
timeout: 5s
37+
retries: 3
1338

1439
backend:
1540
build: ./server
41+
container_name: codequest-backend
42+
restart: unless-stopped
1643
ports:
1744
- "5000:5000"
1845
volumes:
1946
- ./server:/app
2047
- /app/node_modules
2148
environment:
2249
- NODE_ENV=development
50+
- MONGO_URI=mongodb://admin:password@mongo:27017/codequest?authSource=admin
51+
- REDIS_URL=redis://redis:6379
52+
depends_on:
53+
mongo:
54+
condition: service_healthy
55+
redis:
56+
condition: service_healthy
2357
networks:
2458
- app_network
59+
healthcheck:
60+
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
61+
interval: 30s
62+
timeout: 10s
63+
retries: 3
64+
65+
frontend:
66+
build:
67+
context: ./client-test
68+
dockerfile: Dockerfile
69+
container_name: codequest-frontend
70+
restart: unless-stopped
71+
ports:
72+
- "5173:80"
73+
depends_on:
74+
backend:
75+
condition: service_healthy
76+
networks:
77+
- app_network
78+
79+
volumes:
80+
mongo_data:
81+
redis_data:
2582

2683
networks:
2784
app_network:

server/.dockerignore

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
1-
node_modules
2-
npm-debug.log
3-
.env
1+
const dockerIgnorePatterns = [
2+
"node_modules",
3+
"npm-debug.log*",
4+
"yarn-debug.log*",
5+
"yarn-error.log*",
6+
".npm",
7+
".yarn",
8+
".env*",
9+
"!.env.example",
10+
".git",
11+
".gitignore",
12+
"README.md",
13+
".DS_Store",
14+
".vscode",
15+
".idea",
16+
"*.swp",
17+
"*.swo",
18+
"*~",
19+
"logs",
20+
"*.log",
21+
"coverage",
22+
".nyc_output",
23+
".cache",
24+
"dist",
25+
"build",
26+
".tmp",
27+
"temp",
28+
"tmp",
29+
".DS_Store",
30+
"Thumbs.db",
31+
"*.tmp",
32+
"*.temp",
33+
"Dockerfile*",
34+
"docker-compose*.yml",
35+
".dockerignore"
36+
];
37+
38+
module.exports = dockerIgnorePatterns;

server/Dockerfile

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
1-
# FROM node:18
2-
# WORKDIR /app
3-
# COPY package*.json ./
4-
# RUN npm install
5-
# COPY . .
6-
# EXPOSE 5000
7-
# CMD ["npm", "start"]
8-
9-
# Multi-stage build
10-
FROM node:18-alpine AS builder
1+
# Multi-stage build for production
2+
FROM node:20-alpine AS builder
113
WORKDIR /app
4+
5+
# Install dependencies for building
126
COPY package*.json ./
13-
RUN npm install --production
7+
RUN npm ci --only=production && npm cache clean --force
8+
9+
# Copy source code
1410
COPY . .
1511

16-
FROM node:18-alpine
12+
# Production stage
13+
FROM node:20-alpine AS production
1714
WORKDIR /app
18-
COPY --from=builder /app/node_modules ./node_modules
19-
COPY --from=builder /app ./
15+
16+
# Create non-root user for security
17+
RUN addgroup -g 1001 -S nodejs && \
18+
adduser -S nextjs -u 1001
19+
20+
# Copy built application and dependencies
21+
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
22+
COPY --from=builder --chown=nextjs:nodejs /app ./
23+
24+
# Switch to non-root user
25+
USER nextjs
26+
27+
# Expose port
2028
EXPOSE 5000
29+
30+
# Health check
31+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
32+
CMD node -e "require('http').get('http://localhost:5000/health', (res) => { \
33+
process.exit(res.statusCode === 200 ? 0 : 1) \
34+
}).on('error', () => process.exit(1))"
35+
36+
# Start the application
2137
CMD ["npm", "start"]

server/app.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,45 @@ export const cleanup = async () => {
8181

8282
app.get('/hello', (req, res) => { return res.status(200).send("Hello, World!") })
8383

84+
app.get('/health', async (req, res) => {
85+
try {
86+
const mongoose = await import('mongoose');
87+
const dbStatus = mongoose.connection.readyState === 1 ? 'healthy' : 'unhealthy';
88+
89+
let redisStatus = 'not_configured';
90+
if (process.env.REDIS_URL) {
91+
try {
92+
const redis = await import('redis');
93+
const client = redis.createClient({ url: process.env.REDIS_URL });
94+
await client.connect();
95+
await client.ping();
96+
await client.disconnect();
97+
redisStatus = 'healthy';
98+
} catch (error) {
99+
redisStatus = 'unhealthy';
100+
}
101+
}
102+
103+
const health = {
104+
status: dbStatus === 'healthy' ? 'healthy' : 'unhealthy',
105+
timestamp: new Date().toISOString(),
106+
services: {
107+
database: dbStatus,
108+
redis: redisStatus
109+
},
110+
uptime: process.uptime()
111+
};
112+
113+
res.status(health.status === 'healthy' ? 200 : 503).json(health);
114+
} catch (error) {
115+
res.status(503).json({
116+
status: 'unhealthy',
117+
timestamp: new Date().toISOString(),
118+
error: error.message
119+
});
120+
}
121+
});
122+
84123
// 404 handler for unmatched routes
85124
app.use('*', (req, res, next) => {
86125
const error = new Error(`Route ${req.originalUrl} not found`);

server/server.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ const startServer = async () => {
4141
try {
4242
await connectDB();
4343

44-
const PORT = process.env.PORT || 5000;
45-
app.listen(PORT, () => {
46-
console.log(`Server is running at ${PORT}`);
47-
});
44+
// const PORT = process.env.PORT || 5000;
45+
// app.listen(PORT, () => {
46+
// console.log(`Server is running at ${PORT}`);
47+
// });
4848
} catch (error) {
4949
console.error('Failed to start server:', error.message);
5050
process.exit(1);
@@ -53,4 +53,4 @@ const startServer = async () => {
5353

5454
startServer();
5555

56-
// export const handler = serverless(app);
56+
export const handler = serverless(app);

0 commit comments

Comments
 (0)