Bạn có biết?
Bạn có bao giờ deploy một ứng dụng Node.js lên server, rồi phải cài đặt nginx, cấu hình proxy, systemd services, và tự hỏi “sao phải phức tạp vậy không?”
Với Docker, bạn chỉ cần một file docker-compose.yml — chạy nginx + Node.js + database trong vài giây!
Kiến trúc Web App đơn giản
Chúng ta sẽ deploy một web app có:
- ✅ Node.js API — Express server xử lý business logic
- ✅ Nginx — Reverse proxy và static files
- ✅ Communicate — Nginx forward requests tới Node.js
Project Structure
my-webapp/
├── docker-compose.yml
├── nginx/
│ └── default.conf
├── app/
│ ├── package.json
│ ├── index.js
│ └── Dockerfile
└── public/
└── index.html
Node.js App
1. package.json
{
"name": "my-webapp",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
2. index.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
// API endpoint
app.get('/api/hello', (req, res) => {
res.json({ message: 'Hello from Node.js API!' });
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(\`Server running on port \${PORT}\`);
});
3. App Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY index.js ./
EXPOSE 3000
CMD ["node", "index.js"]
Nginx Configuration
default.conf
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://node-app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
location /health {
proxy_pass http://node-app:3000/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
Nginx Dockerfile
FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/default.conf
COPY public/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Docker Compose
docker-compose.yml
version: '3.8'
services:
nginx:
build: ./nginx
ports:
- "80:80"
depends_on:
- node-app
networks:
- webapp-net
restart: unless-stopped
node-app:
build: ./app
environment:
- NODE_ENV=production
- PORT=3000
networks:
- webapp-net
restart: unless-stopped
networks:
webapp-net:
driver: bridge
Workflow
# Build và chạy
$ docker-compose up -d
# Xem logs
$ docker-compose logs -f
# Xem status
$ docker-compose ps
# Stop
$ docker-compose down
# Rebuild sau khi thay đổi code
$ docker-compose up -d --build
Production Considerations
1. Health Checks
# docker-compose.yml với health checks
version: '3.8'
services:
nginx:
build: ./nginx
ports:
- "80:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
node-app:
build: ./app
healthcheck:
test: ["CMD", "wget", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
2. Resource Limits
node-app:
build: ./app
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
3. Environment Variables
version: '3.8'
services:
node-app:
build:
context: ./app
args:
NODE_ENV: production
environment:
- NODE_ENV=${NODE_ENV:-production}
- DB_HOST=${DB_HOST:-localhost}
- DB_PORT=${DB_PORT:-5432}
env_file:
- .env
4. Multiple Environments
# Development
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Production
$ docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
5. SSL/HTTPS với Nginx
# Cấu hình SSL
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.crt;
ssl_certificate_key /etc/nginx/ssl/cert.key;
location / {
proxy_pass http://node-app:3000;
...
}
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Multi-stage Dockerfile cho Node.js
Cách này gọn hơn — build và run trong một image:
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
Flow Request
| Bước | Thành phần | Mô tả |
|---|---|---|
| 1 | Browser | Gửi request tới nginx:80 |
| 2 | Nginx | Nhận request, rewrite URL |
| 3 | Nginx | Proxy_pass tới node-app:3000 |
| 4 | Node.js | Xử lý business logic |
| 5 | Node.js | Return JSON response |
| 6 | Nginx | Forward response về browser |
Best Practices
- Không dùng Alpine cho dev — Alpine bỏ debug symbols, khó debug
- Luôn có health checks — Docker sẽ restart container nếu fails
- Giới hạn resources — Tránh container chiếm full server
- Environment variables — Dùng env_file cho secrets
- SSL trong production — Nginx handle HTTPS
- Logging to stdout — Docker sẽ tự thu thập logs
- Multi-stage build — Giảm image size
- Non-root user — Bảo mật
- Graceful shutdown — Xử lý SIGTERM
- Separate dev và prod — Dùng docker-compose.*.yml
Bước tiếp theo
Tiếp theo, hãy tìm hiểu cách chạy database với Docker!
👉 Đọc tiếp: Docker cho Database: MySQL, PostgreSQL, Redis