Docker Integration
Metadata
| Created | Updated | Status |
|---|---|---|
| 2026-01-28 | 2026-01-31 | ✅ Done |
Overview
Multi-stage Docker containerization for monorepo with dev/prod configurations. VPS deployment via GitHub Actions CI/CD.
Problem: Manual VPS deployment causes environment inconsistencies and conflicts with FastPanel services.
Solution: Docker containers isolate apps with bundled deps. VPS-direct builds (no registry), multi-stage images (~200MB), FastPanel Nginx proxy for SSL.
Architecture
Local Dev: pnpm dev (web:7979, api:3001) → PostgreSQL container
VPS Prod:
FastPanel Nginx (:80/:443)
├─ app.quangnguyen.dev → web (localhost:7979)
└─ api.quangnguyen.dev → api (localhost:3001)
Docker Compose: web ↔ api ↔ postgres (pgdata volume)
Files
| File | Purpose |
|---|---|
apps/web/Dockerfile | Next.js standalone image |
apps/api/Dockerfile | Hono bundled image |
docker-compose.dev.yml | Local PostgreSQL only |
docker-compose.prod.yml | VPS full stack |
.dockerignore | Exclude build context |
.env.*.example | Environment templates |
deploy.sh | VPS deployment script |
.github/workflows/deploy.yml | CI/CD workflow |
Dockerfiles
Web (Next.js)
# deps → builder → runner (3 stages)
FROM node:22-alpine AS runner
COPY --from=builder /app/apps/web/.next/standalone ./
USER nextjs
EXPOSE 7979
CMD ["node", "apps/web/server.js"]
output: 'standalone'reduces image ~70%- Non-root user
nextjs:1001
API (Hono)
FROM node:22-alpine AS runner
COPY --from=builder /app/apps/api/dist ./dist
USER honojs
EXPOSE 3001
CMD ["node", "dist/index.js"]
tsupbundles all deps (no node_modules)- Non-root user
honojs:1001
CI/CD
on:
push:
branches: [dev, main]
jobs:
deploy-dev:
if: github.ref == 'refs/heads/dev'
steps:
- uses: appleboy/ssh-action@v1.0.3
with:
script: |
cd /path/to/app
git pull origin dev
docker compose -f docker-compose.prod.yml up -d --build
| Branch | Environment |
|---|---|
dev | app.quangnguyen.dev |
main | quangnguyen.com (later) |
GitHub Secrets
VPS_HOST, VPS_USER, VPS_SSH_KEY (dev)
PROD_VPS_HOST, PROD_VPS_USER, PROD_VPS_SSH_KEY, PROD_APP_PATH (prod)
Usage
# Local dev
docker compose -f docker-compose.dev.yml up -d
pnpm dev
# VPS deploy
./deploy.sh api # or web, all
# Logs
docker compose -f docker-compose.prod.yml logs -f
# Database
docker exec -it flowershop-postgres psql -U flowershop
docker exec flowershop-postgres pg_dump -U flowershop flowershop > backup.sql
Environment Variables
# Dev
DATABASE_URL=postgresql://flowershop:devpassword@localhost:5432/flowershop_dev
# Prod (use 'postgres' hostname, not 'localhost')
DATABASE_URL=postgresql://flowershop:<password>@postgres:5432/flowershop
NEXT_PUBLIC_API_URL=https://api.quangnguyen.dev
FRONTEND_URL=https://app.quangnguyen.dev
Troubleshooting
| Issue | Solution |
|---|---|
| Container exits | docker logs flowershop-api - check env vars, db connection |
| 502 Bad Gateway | docker ps, curl http://127.0.0.1:7979, nginx -t |
| DB connection failed | Verify postgres running, DATABASE_URL uses postgres hostname |
| Build fails | docker builder prune, rebuild with --no-cache |
Security
- Non-root container users
.env.prodpermissions:chmod 600- Ports bound to
127.0.0.1only - FastPanel manages SSL (Let's Encrypt)
- GitHub Secrets for credentials