From 8c34460889ffd3ce8e69df8c2605ac837c191c65 Mon Sep 17 00:00:00 2001 From: Phuoc Nguyen Date: Fri, 10 Oct 2025 17:13:45 +0700 Subject: [PATCH] update docker --- DEPLOYMENT.md | 350 ++++++++++++++++++++++++++++++++++++++++ Dockerfile | 2 +- build-and-push.sh | 27 ++++ docker-compose.prod.yml | 78 +++++++++ docker-compose.yml | 71 +++----- 5 files changed, 478 insertions(+), 50 deletions(-) create mode 100644 DEPLOYMENT.md create mode 100755 build-and-push.sh create mode 100644 docker-compose.prod.yml diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..9501804 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,350 @@ +# Docker Deployment Guide + +## Overview + +This project can be deployed using Docker in two ways: +1. **Build and Push to Registry** (Recommended for production) +2. **Build from Source** (Good for development/testing) + +--- + +## Option 1: Using Pre-built Image (Recommended) + +### Step 1: Build and Push Image + +On your **local machine or CI/CD**: + +```bash +# 1. Login to Docker Hub (or your registry) +docker login + +# 2. Build the image +docker build -t retail-pos-api:latest . + +# 3. Tag for your registry +docker tag retail-pos-api:latest your-username/retail-pos-api:latest + +# 4. Push to registry +docker push your-username/retail-pos-api:latest +``` + +Or use the automated script: +```bash +# Edit the script first to set your registry username +nano build-and-push.sh + +# Run the script +./build-and-push.sh +``` + +### Step 2: Deploy on Target Server + +On your **deployment server**: + +```bash +# 1. Copy docker-compose.prod.yml to server +scp docker-compose.prod.yml user@server:/path/to/deployment/ + +# 2. SSH into server +ssh user@server + +# 3. Edit docker-compose.prod.yml to update image name and env vars +nano docker-compose.prod.yml + +# 4. Pull and start services +docker-compose -f docker-compose.prod.yml pull +docker-compose -f docker-compose.prod.yml up -d + +# 5. Check status +docker-compose -f docker-compose.prod.yml ps +docker-compose -f docker-compose.prod.yml logs -f api +``` + +--- + +## Option 2: Build from Source + +### On Target Server + +```bash +# 1. Clone repository (or copy files) +git clone your-repo-url +cd retail-nest + +# 2. Create/edit .env file if needed +cp .env.example .env +nano .env + +# 3. Build and start +docker-compose up -d --build + +# 4. Check status +docker-compose ps +docker-compose logs -f api +``` + +--- + +## File Structure + +``` +retail-nest/ +├── Dockerfile # Multi-stage build configuration +├── docker-compose.yml # For local development (builds from source) +├── docker-compose.prod.yml # For production (uses pre-built image) +├── build-and-push.sh # Automated build and push script +└── .dockerignore # Files to exclude from Docker build +``` + +--- + +## Environment Variables + +All environment variables can be configured in `docker-compose.prod.yml`: + +### Database (Aiven PostgreSQL) +- `DB_HOST`: Your Aiven database host +- `DB_PORT`: Database port (usually 20912 for Aiven) +- `DB_USERNAME`: Database username +- `DB_PASSWORD`: Database password +- `DB_DATABASE`: Database name +- `DB_SSL`: "true" (required for Aiven) + +### Application +- `NODE_ENV`: production +- `PORT`: 3000 +- `API_PREFIX`: api +- `JWT_SECRET`: Your JWT secret key +- `CORS_ORIGIN`: Allowed origins + +### Redis +- `REDIS_HOST`: redis (container name) +- `REDIS_PORT`: 6379 +- `CACHE_TTL`: 300 + +--- + +## Useful Commands + +### Development (docker-compose.yml) +```bash +# Start services (builds from source) +docker-compose up -d + +# View logs +docker-compose logs -f api + +# Restart API +docker-compose restart api + +# Stop services +docker-compose down + +# Rebuild +docker-compose up -d --build +``` + +### Production (docker-compose.prod.yml) +```bash +# Pull latest image and start +docker-compose -f docker-compose.prod.yml pull +docker-compose -f docker-compose.prod.yml up -d + +# View logs +docker-compose -f docker-compose.prod.yml logs -f api + +# Restart API +docker-compose -f docker-compose.prod.yml restart api + +# Stop services +docker-compose -f docker-compose.prod.yml down + +# Update to new version +docker-compose -f docker-compose.prod.yml pull +docker-compose -f docker-compose.prod.yml up -d +``` + +### Docker Commands +```bash +# View running containers +docker ps + +# View images +docker images + +# Remove unused images +docker image prune -a + +# View logs of specific container +docker logs retail-pos-api -f + +# Execute command in container +docker exec -it retail-pos-api sh + +# Check health +docker inspect retail-pos-api --format='{{.State.Health.Status}}' +``` + +--- + +## Registry Options + +### Docker Hub (Public/Private) +```bash +# Login +docker login + +# Build and push +docker build -t your-username/retail-pos-api:latest . +docker push your-username/retail-pos-api:latest +``` + +### GitHub Container Registry +```bash +# Login +echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin + +# Build and push +docker build -t ghcr.io/username/retail-pos-api:latest . +docker push ghcr.io/username/retail-pos-api:latest +``` + +### Private Registry +```bash +# Login +docker login registry.your-domain.com + +# Build and push +docker build -t registry.your-domain.com/retail-pos-api:latest . +docker push registry.your-domain.com/retail-pos-api:latest +``` + +--- + +## Health Checks + +The API includes health check endpoints: + +- **Application Health**: `http://localhost:3000/api/health` +- **Root Health**: `http://localhost:3000/api` + +Docker automatically monitors these endpoints and will restart the container if unhealthy. + +--- + +## Troubleshooting + +### Container won't start +```bash +# Check logs +docker-compose logs api + +# Check environment variables +docker exec retail-pos-api env + +# Verify database connection +docker exec retail-pos-api wget -qO- http://localhost:3000/api/health +``` + +### Database connection issues +- Verify `DB_SSL` is set to "true" for Aiven +- Check database credentials in docker-compose.prod.yml +- Ensure firewall allows connections to Aiven + +### Redis connection issues +```bash +# Check Redis is running +docker ps | grep redis + +# Test Redis connection +docker exec retail-pos-redis redis-cli ping +``` + +### Image pull fails +```bash +# Re-login to registry +docker login + +# Check image name is correct +docker pull your-username/retail-pos-api:latest +``` + +--- + +## Security Best Practices + +1. **Never commit secrets**: Keep sensitive data in environment variables +2. **Use private registry**: For production deployments +3. **Regular updates**: Keep base images updated +4. **Scan images**: Use `docker scan` to check for vulnerabilities +5. **Use secrets management**: Consider Docker secrets or external vaults +6. **Limit resources**: Add resource limits in docker-compose.yml + +--- + +## CI/CD Integration + +### GitHub Actions Example +```yaml +name: Build and Push Docker Image + +on: + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: your-username/retail-pos-api:latest +``` + +--- + +## Monitoring + +### Check service status +```bash +docker-compose ps +``` + +### View resource usage +```bash +docker stats retail-pos-api retail-pos-redis +``` + +### Access API documentation +``` +http://localhost:3000/api/docs +``` + +--- + +## Backup and Restore + +### Redis Data +```bash +# Backup +docker exec retail-pos-redis redis-cli SAVE +docker cp retail-pos-redis:/data/dump.rdb ./backup/ + +# Restore +docker cp ./backup/dump.rdb retail-pos-redis:/data/ +docker-compose restart redis +``` + +### Database (Aiven handles backups automatically) +Check your Aiven console for backup options. diff --git a/Dockerfile b/Dockerfile index 678f8fb..c2c975a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Multi-stage build for optimized production image # Stage 1: Build -FROM node:18-alpine AS builder +FROM node:22-alpine AS builder WORKDIR /app diff --git a/build-and-push.sh b/build-and-push.sh new file mode 100755 index 0000000..adbba2a --- /dev/null +++ b/build-and-push.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Build and Push Docker Image Script +# This script builds the Docker image and pushes it to a registry + +set -e # Exit on error + +# Configuration +IMAGE_NAME="retail-backend" +REGISTRY="renolation" # Change this to your Docker Hub username or registry URL +VERSION="latest" +FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${VERSION}" + +echo "🏗️ Building Docker image..." +docker build -t ${IMAGE_NAME}:${VERSION} . + +echo "🏷️ Tagging image for registry..." +docker tag ${IMAGE_NAME}:${VERSION} ${FULL_IMAGE} + +echo "📤 Pushing image to registry..." +docker push ${FULL_IMAGE} + +echo "✅ Successfully built and pushed: ${FULL_IMAGE}" +echo "" +echo "To use this image on another server, run:" +echo " docker pull ${FULL_IMAGE}" +echo " docker-compose -f docker-compose.prod.yml up -d" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..0eefa63 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,78 @@ +version: '3.8' + +services: + # Redis Cache + redis: + image: redis:7-alpine + container_name: retail-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis-data:/data + networks: + - retail-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # NestJS API Application (Using pre-built image) + api: + image: renolation/retail-backend:latest # Change this to your Docker Hub or registry URL + container_name: retail-backend + restart: unless-stopped + ports: + - "3000:3000" + environment: + # Application + NODE_ENV: production + PORT: 3000 + API_PREFIX: api + + # Database (Aiven PostgreSQL) + DB_HOST: pg-30ed1d6a-renolation.b.aivencloud.com + DB_PORT: 20912 + DB_USERNAME: avnadmin + DB_PASSWORD: AVNS_b5AVrZdm2M2donLgXXQ + DB_DATABASE: defaultdb + DB_SSL: "true" + + # JWT Configuration + JWT_SECRET: retail-pos-super-secret-key-change-in-production-2025 + JWT_EXPIRES_IN: 1d + + # Redis Cache + REDIS_HOST: redis + REDIS_PORT: 6379 + CACHE_TTL: 300 + + # CORS + CORS_ORIGIN: http://localhost:3000,capacitor://localhost + + # Rate Limiting + THROTTLE_TTL: 60 + THROTTLE_LIMIT: 100 + + # Bcrypt + BCRYPT_ROUNDS: 10 + depends_on: + redis: + condition: service_healthy + networks: + - retail-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + redis-data: + driver: local + +networks: + retail-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index a347ce1..d6c7e0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,27 +1,6 @@ version: '3.8' services: - # PostgreSQL Database - postgres: - image: postgres:15-alpine - container_name: retail-pos-postgres - restart: unless-stopped - environment: - POSTGRES_DB: retail_pos - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - "5432:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - networks: - - retail-pos-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 5s - retries: 5 - # Redis Cache redis: image: redis:7-alpine @@ -39,67 +18,61 @@ services: timeout: 5s retries: 5 - # NestJS Application + # NestJS API Application api: build: context: . dockerfile: Dockerfile + target: production container_name: retail-pos-api restart: unless-stopped ports: - "3000:3000" environment: + # Application NODE_ENV: production PORT: 3000 API_PREFIX: api - DB_HOST: postgres - DB_PORT: 5432 - DB_USERNAME: postgres - DB_PASSWORD: postgres - DB_DATABASE: retail_pos + + # Database (Aiven PostgreSQL) + DB_HOST: pg-30ed1d6a-renolation.b.aivencloud.com + DB_PORT: 20912 + DB_USERNAME: avnadmin + DB_PASSWORD: AVNS_b5AVrZdm2M2donLgXXQ + DB_DATABASE: defaultdb + DB_SSL: "true" + + # JWT Configuration JWT_SECRET: retail-pos-super-secret-key-change-in-production-2025 JWT_EXPIRES_IN: 1d + + # Redis Cache REDIS_HOST: redis REDIS_PORT: 6379 CACHE_TTL: 300 + + # CORS CORS_ORIGIN: http://localhost:3000,capacitor://localhost + + # Rate Limiting THROTTLE_TTL: 60 THROTTLE_LIMIT: 100 + + # Bcrypt BCRYPT_ROUNDS: 10 depends_on: - postgres: - condition: service_healthy redis: condition: service_healthy networks: - retail-pos-network healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s - # Optional: pgAdmin for database management - pgadmin: - image: dpage/pgadmin4:latest - container_name: retail-pos-pgadmin - restart: unless-stopped - environment: - PGADMIN_DEFAULT_EMAIL: admin@retailpos.com - PGADMIN_DEFAULT_PASSWORD: admin123 - ports: - - "5050:80" - depends_on: - - postgres - networks: - - retail-pos-network - profiles: - - tools - volumes: - postgres-data: - driver: local redis-data: driver: local