Deploy Checklist — Glamvoo Production
Checklist 1-shot: làm tuần tự từ trên xuống. Mỗi mục check xong thì tick
[x]. Reference chi tiết:docker-deploy.md.
Phase 1 — One-time setup (chỉ làm 1 lần đầu)
1.1 GitHub — Container Registry & Secrets
Repos cần có (đã xong):
-
novagoo/booking-api— pushed -
novagoo/booking-web— pushed -
novagoo/booking-docs— pushed
booking-web Variables (https://github.com/novagoo/booking-web/settings/variables/actions → Variables tab):
| Name | Value |
|---|---|
- [ ] NEXT_PUBLIC_API_URL |
https://glamvoo.com/api |
- [ ] NEXT_PUBLIC_APP_NAME |
Glamvoo |
- [ ] NEXT_PUBLIC_APP_SHORT_NAME |
GV |
- [ ] NEXT_PUBLIC_STORAGE_BUCKET |
booking-assets ← phải khớp STORAGE_BUCKET ở §1.2 |
booking-web Secrets (Secrets tab):
| Name | Value |
|---|---|
- [ ] NEXT_PUBLIC_GOOGLE_CLIENT_ID |
Google OAuth Client ID (giống dev) |
Verify: re-run workflow để image có env đúng:
gh workflow run "Build & Push booking-web" --repo novagoo/booking-web --ref main
gh run watch --repo novagoo/booking-web
- Build success (xanh)
1.2 VNPT S3
- Login VNPT cloud console
- Tạo bucket tên
booking-assets(PHẢI giốngNEXT_PUBLIC_STORAGE_BUCKET) - Set bucket private (default)
- Tạo Access Key riêng cho prod (best practice) HOẶC dùng key dev:
- Copy
STORAGE_ACCESS_KEY - Copy
STORAGE_SECRET_KEY
- Copy
1.3 DNS — point domain về VPS
Trên DNS provider (Cloudflare/Namecheap/etc.):
| Record | Type | Value |
|---|---|---|
- [ ] glamvoo.com |
A | <IP VPS> |
- [ ] www.glamvoo.com |
A | <IP VPS> (hoặc CNAME → glamvoo.com) |
- [ ] images.glamvoo.com |
A | <IP VPS> |
- [ ] docs.glamvoo.com |
A | <IP VPS> |
Verify DNS đã propagate:
dig +short glamvoo.com
dig +short www.glamvoo.com
dig +short images.glamvoo.com
dig +short docs.glamvoo.com
# Cả 4 phải trả về IP VPS
1.4 Server bootstrap
Postgres (đã xong nếu user/db/listen_addresses đã set)
- User
glamvoo_user+ DBglamvootạo -
listen_addresses = '*'+ restart -
pg_hba.confthêmhost glamvoo glamvoo_user 172.16.0.0/12 scram-sha-256+ reload
Verify:
sudo ss -tlnp | grep 5432 # phải thấy 0.0.0.0:5432
UFW
-
sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow from 172.16.0.0/12 to any port 5432 proto tcp sudo ufw enable
> KHÔNG `ufw deny 5432/tcp` — sẽ block luôn allow specific. Default policy đã deny.
Verify: `sudo ufw status` → 22/80/443 ALLOW.
#### Docker
- [ ] `curl -fsSL https://get.docker.com | sh`
- [ ] `sudo usermod -aG docker $USER`
- [ ] `sudo systemctl enable --now docker`
- [ ] `newgrp docker`
- [ ] `docker --version` → in version
#### TLS certs
- [ ] `sudo certbot certonly --standalone -d glamvoo.com -d www.glamvoo.com`
- [ ] `sudo certbot certonly --standalone -d images.glamvoo.com`
- [ ] `sudo certbot certonly --standalone -d docs.glamvoo.com`
Verify:
```bash
ls /etc/letsencrypt/live/glamvoo.com/ # fullchain.pem + privkey.pem
ls /etc/letsencrypt/live/images.glamvoo.com/ # fullchain.pem + privkey.pem
ls /etc/letsencrypt/live/docs.glamvoo.com/ # fullchain.pem + privkey.pem
Basic auth cho docs.glamvoo.com
Docs site public-accessible nhưng cần password để xem (internal only).
# Cài htpasswd tool
sudo apt install -y apache2-utils
# Tạo file (lệnh `-c` = create new, ghi đè nếu có)
htpasswd -c /opt/booking-system/nginx/.htpasswd <username>
# Nhập password 2 lần
# Thêm user khác (KHÔNG -c kẻo ghi đè)
htpasswd /opt/booking-system/nginx/.htpasswd <username2>
- Tạo
nginx/.htpasswdvới ít nhất 1 user TRƯỚC khidocker compose up nginx
⚠️ Quan trọng: tạo
.htpasswd(file) trước khi start nginx container. Nếu start nginx trước → Docker bind-mount path không tồn tại → tự tạo DIRECTORY rỗng → nginx restart loop. Fix:docker compose stop nginx && rmdir nginx/.htpasswd && htpasswd -c nginx/.htpasswd admin && docker compose up -d nginx.File
nginx/.htpasswdđã được gitignore, không commit. Chỉ tồn tại trên server.
GHCR login
- Tạo PAT classic ở https://github.com/settings/tokens với scope
read:packages - Nếu org
novagooenable SSO → click "Configure SSO" trên token, authorize chonovagoo -
echo ghp_xxxxx | docker login ghcr.io -u <USERNAME> --password-stdin→Login Succeeded
Verify:
docker pull ghcr.io/novagoo/booking-api:latest
# Phải pull được
1.5 Clone deploy stack + config env
sudo mkdir -p /opt/booking-system
sudo chown $USER:$USER /opt/booking-system
cd /opt/booking-system
git clone git@github.com:novagoo/booking-docs.git .
cp .env.example .env
- Clone OK
Generate keys (paste output vào .env step kế):
echo "JWT_SECRET=$(openssl rand -hex 32)"
echo "PAYMENT_ENCRYPTION_KEY=$(openssl rand -hex 32)"
echo "IMGPROXY_KEY=$(openssl rand -hex 32)"
echo "IMGPROXY_SALT=$(openssl rand -hex 32)"
Edit .env (nano .env):
| Field | Value |
|---|---|
- [ ] DATABASE_URL |
Thay STRONG_PASSWORD bằng pass glamvoo_user |
- [ ] JWT_SECRET |
Paste từ openssl |
- [ ] BULL_BOARD_PASS |
Password mạnh tự chọn |
- [ ] SMTP_HOST |
console (dev test) hoặc SMTP provider thật |
- [ ] GOOGLE_CLIENT_ID |
Google OAuth Client ID (giống NEXT_PUBLIC_GOOGLE_CLIENT_ID) |
- [ ] PAYMENT_ENCRYPTION_KEY |
Paste từ openssl |
- [ ] STORAGE_ACCESS_KEY |
VNPT key |
- [ ] STORAGE_SECRET_KEY |
VNPT secret |
- [ ] STORAGE_BUCKET |
booking-assets (giống GHA Variable) |
- [ ] IMGPROXY_KEY |
Paste từ openssl |
- [ ] IMGPROXY_SALT |
Paste từ openssl |
Lưu file (Ctrl+O → Enter → Ctrl+X).
Phase 2 — First deploy
cd /opt/booking-system
./deploy.sh
- Script chạy tới end, không lỗi
-
docker compose -f docker-compose.prod.yml ps→ tất cả servicehealthy
Verify:
curl -i https://glamvoo.com/api/health
# Phải trả 200 + JSON {"status":"ok",...}
- Health 200 OK
Mở browser:
-
https://glamvoo.com→ web load OK -
https://www.glamvoo.com→ 301 redirect về root -
https://images.glamvoo.com→ 404 (đúng — chưa có signed URL nào để test, nhưng response phải đến từ imgproxy)
Phase 3 — Tạo super-admin lần đầu
docker compose -f docker-compose.prod.yml exec api yarn reset:superadmin
# Nhập email + password (min 6 ký tự)
- Super-admin tạo OK
- Login
https://glamvoo.com/admin→ vào được dashboard
Phase 4 — Subsequent deploys (lần thứ 2+)
Khi push code mới lên booking-api hoặc booking-web:
- Trên dev —
git push→ GHA tự build image (~2-3 phút) - Trên server:
cd /opt/booking-system ./deploy.sh
Đặc biệt:
- Scale:
API_REPLICAS=4 ./deploy.sh - Rollback:
IMAGE_TAG=sha-<old-sha> ./deploy.sh - Logs:
docker compose -f docker-compose.prod.yml logs -f api
Common pitfalls
| Triệu chứng | Fix |
|---|---|
docker login ghcr.io denied |
PAT chưa SSO authorize cho org novagoo (xem §1.4) |
connection refused đến Postgres |
pg_hba.conf chưa allow 172.16.0.0/12, hoặc listen_addresses vẫn là localhost |
| Image avatar/portfolio không hiện | NEXT_PUBLIC_STORAGE_BUCKET GHA ≠ STORAGE_BUCKET .env. Hoặc bucket VNPT khác tên |
| Throttler chặn nhầm IP | TRUST_PROXY chưa set 1 (đã set sẵn ở compose, không cần làm gì) |
502 sau deploy |
Đợi healthcheck pass — deploy.sh đã handle, nhưng khi update DNS/cert thì có blip |
images.glamvoo.com 502 |
Cert subdomain chưa issue, hoặc DNS chưa propagate |