Triển khai ứng dụng Node.js bằng Portainer: build image, env, domain, SSL
Triển khai Node.js không chỉ là “chạy npm start trên server”. Khi app đi vào production, bạn cần image ổn định, biến môi trường an toàn, domain trỏ đúng, SSL hợp lệ, log dễ kiểm tra, restart tự động. Làm thủ công bằng Docker CLI vẫn được, nhưng khi nhiều app, nhiều container, nhiều network → dễ rối.
1. Kiến trúc triển khai đề xuất
Mô hình phổ biến:
– VPS/Linux server → chạy Docker. – Portainer → quản lý Docker qua UI. – Node.js app → đóng gói bằng Docker image. – Reverse proxy → Nginx Proxy Manager, Traefik, Caddy hoặc Nginx. – Domain → trỏ DNS về IP server. – SSL → Let’s Encrypt.
Luồng request:
User → https://example.com → Reverse Proxy → Node.js container:3000Node.js app không nhất thiết expose trực tiếp ra internet. Thường chỉ reverse proxy public port 80/443, còn app chạy nội bộ trong Docker network.
2. Chuẩn bị server
Cần:
– VPS Ubuntu/Debian.
– Domain.
– Docker đã cài.
– Portainer đã chạy.
– Repo Node.js có Dockerfile.
Cài Docker nhanh:
curl -fsSL https://get.docker.com | shChạy Portainer:
docker volume create portainer_data
docker run -d
-p 9000:9000
-p 9443:9443
--name portainer
--restart=always
-v /var/run/docker.sock:/var/run/docker.sock
-v portainer_data:/data
portainer/portainer-ce:latest
Truy cập:
https://YOUR_SERVER_IP:9443Tạo admin user → chọn local Docker environment.
3. Viết Dockerfile cho Node.js
Ví dụ app Express/Nest/Next custom server đều tương tự.
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Nếu app cần build trước, ví dụ TypeScript/NestJS:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]
Điểm quan trọng:
– Dùng npm ci → build ổn định.
– Không copy .env vào image.
– Không expose DB password trong Dockerfile.
– Dùng multi-stage build → image nhẹ hơn.
– App phải listen trên 0.0.0.0, không phải localhost.
Ví dụ Express:
app.listen(process.env.PORT || 3000, '0.0.0.0')4. Tạo .dockerignore
Không bỏ qua bước này. Nếu không, image có thể chứa file thừa, secret, cache.
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
Dockerfile
docker-compose.yml
README.md.env không nên nằm trong image. Env nên cấu hình ở Portainer/Stack.
5. Build image: 3 cách phổ biến
Cách 1: Build từ Git trong Portainer
Trong Portainer:
Stacks → Add stack → RepositoryNhập:
– Repository URL.
– Branch.
– Compose path, ví dụ docker-compose.yml.
– Git credential nếu repo private.
Portainer sẽ pull repo → deploy theo compose.
Phù hợp khi bạn muốn deploy trực tiếp từ Git.
Cách 2: Build local rồi push registry
Trên máy dev/CI:
docker build -t yourname/node-app:1.0.0 .
docker push yourname/node-app:1.0.0Trên Portainer dùng image:
services:
app:
image: yourname/node-app:1.0.0Ưu điểm:
– CI/CD rõ ràng. – Rollback dễ. – Không build nặng trên VPS. – Version image kiểm soát tốt.
Production nên dùng tag version như 1.0.0, 2025-01-15, commit SHA. Tránh phụ thuộc latest.
Cách 3: Upload Dockerfile trong Portainer
Portainer có thể build image từ UI. Phù hợp test nhanh, nhưng production nên dùng Git/CI.
6. Deploy bằng Docker Compose trong Portainer
Tạo file docker-compose.yml:
services:
node_app:
image: yourname/node-app:1.0.0
container_name: node_app
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
DATABASE_URL: ${DATABASE_URL}
JWT_SECRET: ${JWT_SECRET}
networks:
- web
expose:
- "3000"
networks:
web:
external: true
Trong Portainer:
Stacks → Add stack → Web editorPaste compose → nhập env ở phần Environment variables:
DATABASE_URL=postgresql://user:password@db:5432/app
JWT_SECRET=your-long-secretDeploy stack.
Lưu ý:
– expose chỉ mở trong Docker network.
– ports mở ra host.
– Với reverse proxy, thường dùng expose, không dùng ports.
– Nếu cần debug tạm:
ports:
- "3000:3000"Sau khi xong nên đóng lại.
7. Quản lý biến môi trường an toàn
Env thường gồm:
– NODE_ENV
– PORT
– DATABASE_URL
– REDIS_URL
– JWT_SECRET
– SMTP_HOST
– SMTP_USER
– SMTP_PASS
– S3_ACCESS_KEY
– S3_SECRET_KEY
Nguyên tắc:
– Không commit .env.
– Không hardcode secret trong code.
– Không ghi secret vào Dockerfile.
– Dùng Portainer env hoặc Docker secrets nếu cần bảo mật hơn.
– Rotate secret định kỳ.
– Tách env staging/production.
Trong Node.js, validate env khi app start. Ví dụ:
const required = ['DATABASE_URL', 'JWT_SECRET']
for (const key of required) {
if (!process.env[key]) {
throw new Error(Missing env: ${key})
}
}
App fail sớm → tốt hơn chạy lỗi ngầm.
8. Tạo Docker network cho reverse proxy
Nếu dùng Nginx Proxy Manager hoặc Traefik, app và proxy cần chung network.
Tạo network:
docker network create webNếu reverse proxy đã chạy, attach nó vào network web.
Ví dụ Nginx Proxy Manager compose:
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
volumes:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
networks:
- web
volumes:
npm_data:
npm_letsencrypt:
networks:
web:
external: true
Port 81 là UI quản trị. Nên hạn chế IP hoặc đổi rule firewall nếu public.
9. Trỏ domain về server
Vào DNS provider, tạo record:
A example.com YOUR_SERVER_IP
A www.example.com YOUR_SERVER_IPNếu dùng IPv6:
AAAA example.com YOUR_IPV6Chờ DNS propagate. Kiểm tra:
dig example.comHoặc:
nslookup example.comDomain phải trỏ đúng IP trước khi cấp SSL Let’s Encrypt.
10. Cấu hình reverse proxy với Nginx Proxy Manager
Truy cập NPM:
http://YOUR_SERVER_IP:81Tạo Proxy Host:
Hosts → Proxy Hosts → Add Proxy HostTab Details:
– Domain Names: example.com
– Scheme: http
– Forward Hostname/IP: node_app
– Forward Port: 3000
– Websockets Support: bật nếu app dùng socket
– Block Common Exploits: bật
Vì node_app cùng Docker network với NPM, NPM resolve được container name.
Nếu không resolve được:
– Kiểm tra cùng network chưa.
– Kiểm tra container name.
– Kiểm tra app listen 0.0.0.0.
– Kiểm tra port đúng chưa.
11. Bật SSL Let’s Encrypt
Trong Proxy Host → tab SSL:
– Request a new SSL Certificate. – Chọn domain. – Force SSL: bật. – HTTP/2 Support: bật. – HSTS: cân nhắc bật sau khi chắc chắn HTTPS ổn. – Nhập email. – Agree Let’s Encrypt Terms. – Save.
Nếu lỗi cấp SSL:
– Domain chưa trỏ đúng IP.
– Port 80/443 bị firewall chặn.
– Có service khác chiếm port.
– DNS proxy/CDN gây sai xác thực.
– IPv6 trỏ sai.
Kiểm tra port:
sudo ss -tulpn | grep -E ':80|:443'Kiểm tra firewall:
sudo ufw statusMở port:
sudo ufw allow 80
sudo ufw allow 44312. Kiểm tra logs, health, restart
Trong Portainer:
Containers → node_app → LogsHoặc CLI:
docker logs -f node_appKiểm tra app:
curl -I https://example.comCompose nên có restart policy:
restart: unless-stoppedCó thể thêm healthcheck:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3Trong app tạo endpoint:
app.get('/health', (req, res) => res.send('ok'))Healthcheck giúp phát hiện container lỗi nhanh hơn.
13. Cập nhật phiên bản app
Quy trình an toàn:
1. Build image mới. 2. Push registry. 3. Đổi tag trong stack. 4. Redeploy. 5. Xem logs. 6. Test domain. 7. Rollback nếu lỗi.
Ví dụ:
image: yourname/node-app:1.0.1Trong Portainer:
Stacks → app_stack → Editor → Update the stackRollback:
image: yourname/node-app:1.0.0Không nên sửa code trực tiếp trong container. Container là immutable. Muốn đổi app → build image mới.
14. Một số lỗi thường gặp
App chạy local OK, Docker lỗi
Nguyên nhân thường gặp:
– Thiếu env.
– App listen localhost.
– Thiếu build step.
– Sai CMD.
– Port không khớp.
– File .env không được copy.
Fix:
app.listen(PORT, '0.0.0.0')Reverse proxy báo 502
Kiểm tra:
– Container app có running không. – NPM và app có cùng network không. – Forward hostname đúng container name không. – App port đúng không. – Log app có crash không.
SSL không cấp được
Kiểm tra:
– DNS A record. – Port 80/443. – Firewall. – Cloudflare proxy. – IPv6 sai.
Env không nhận
Trong Portainer stack, nếu dùng ${VAR}, phải khai báo ở Environment variables hoặc env file tương ứng. Sau khi đổi env cần redeploy container.
15. Checklist production ngắn gọn
– Dockerfile multi-stage.
– .dockerignore sạch.
– Image có version tag.
– Env tách khỏi image.
– Container restart: unless-stopped.
– App listen 0.0.0.0.
– Reverse proxy dùng Docker network riêng.
– Domain trỏ đúng IP.
– SSL Let’s Encrypt bật Force SSL.
– Logs kiểm tra sau deploy.
– Backup DB/volume.
– Không expose port app nếu không cần.
– Không dùng secret yếu.
Kết luận
Portainer giúp việc triển khai Node.js bằng Docker trở nên rõ ràng hơn: build image, cấu hình env, redeploy, xem logs, quản lý network đều có UI. Tuy vậy, nền tảng vẫn là Docker đúng chuẩn: image sạch, env an toàn, container không state, reverse proxy tách biệt, SSL đầy đủ.
Quy trình thực tế nên là: code → build image → push registry → Portainer deploy stack → reverse proxy domain → bật SSL → kiểm tra logs. Khi đã chuẩn hóa, bạn có thể triển khai thêm nhiều app Node.js trên cùng server mà vẫn dễ quản lý, dễ rollback, ít lỗi production hơn.
Bình luận (0)
Chưa có bình luận. Hãy là người đầu tiên!