# 🚀 Deploy Web Application

10 min read
Table of Contents

āđ€āļ—āļ„āđ‚āļ™āđ‚āļĨāļĒāļĩāļ—āļĩāđˆāđƒāļŠāđ‰

āđ€āļ­āļāļŠāļēāļĢāļ™āļĩāđ‰āļˆāļ°āđāļ™āļ°āļ™āļģāļāļēāļĢ deploy web application āđ‚āļ”āļĒāđƒāļŠāđ‰āđ€āļ—āļ„āđ‚āļ™āđ‚āļĨāļĒāļĩāļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰

  • Cloudflare: DNS Management & CDN - āļˆāļąāļ”āļāļēāļĢ domain name āđāļĨāļ°āđ€āļžāļīāđˆāļĄāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒ
  • Digital Ocean: Cloud Server Provider - āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļŠāļģāļŦāļĢāļąāļš hosting application
  • Docker: Containerization - āļˆāļąāļ”āļāļēāļĢāđāļĨāļ° package application āđƒāļŦāđ‰āļžāļĢāđ‰āļ­āļĄ deploy
  • Nginx: Web Server & Reverse Proxy - āļĢāļąāļš request āļˆāļēāļ user āđāļĨāļ°āļŠāđˆāļ‡āļ•āđˆāļ­āđ„āļ›āļĒāļąāļ‡ application
  • SSL Certbot: SSL Certificate Management - āļˆāļąāļ”āļāļēāļĢāđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ SSL/TLS āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī

āļ āļēāļžāļĢāļ§āļĄāļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ

Container Architecture

āļĢāļ°āļšāļšāļˆāļ°āđāļĒāļ service āļ­āļ­āļāđ€āļ›āđ‡āļ™ 3 containers āļŦāļĨāļąāļ

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Nginx │ │ Web App │ │ Certbot │
│ Container │ │ Container │ │ Container │
├─────────────────â”Ī ├─────────────────â”Ī ├─────────────────â”Ī
│ â€Ē Reverse Proxy │ │ â€Ē React App │ │ â€Ē SSL Certs │
│ â€Ē SSL Handling │ │ â€Ē Built Assets │ │ â€Ē Auto Renewal │
│ â€Ē Port 80/443 │ │ â€Ē Static Files │ │ â€Ē Background │
└─────────────────┘ └─────────────────┘ └─────────────────┘

Network Flow

Internet → Cloudflare → Nginx Container → Web App Container
↗
Certbot Container (SSL Certs)

āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ

web-app/
├── .deployment/ # āđ„āļŸāļĨāđŒāļŠāļģāļŦāļĢāļąāļš deployment
│ ├── nginx/
│ │ ├── web-app.conf # Nginx config āļŠāļģāļŦāļĢāļąāļš container āļ āļēāļĒāđƒāļ™
│ │ └── web-app-nginx.conf # Nginx config āļŠāļģāļŦāļĢāļąāļš main server
│ ├── certbot-init.sh # Script āļŠāļģāļŦāļĢāļąāļšāļ‚āļ­ SSL āļ„āļĢāļąāđ‰āļ‡āđāļĢāļ
│ └── certbot-renew.sh # Script āļŠāļģāļŦāļĢāļąāļšāļ•āđˆāļ­āļ­āļēāļĒāļļ SSL (Manual)
├── docker-compose.yml # āļāļģāļŦāļ™āļ”āļ„āđˆāļē containers
├── Dockerfile # āļŠāļĢāđ‰āļēāļ‡ Docker image
└── .gitlab-ci.yml # CI/CD pipeline configuration

āļ­āļ˜āļīāļšāļēāļĒāđ„āļŸāļĨāđŒāļŠāļģāļ„āļąāļ:

  • web-app.conf: āļāļģāļŦāļ™āļ”āļ„āđˆāļē Nginx āļŠāļģāļŦāļĢāļąāļš serve static files āļ āļēāļĒāđƒāļ™ container
  • web-app-nginx.conf: āļāļģāļŦāļ™āļ”āļ„āđˆāļē Nginx āļŦāļĨāļąāļāļŠāļģāļŦāļĢāļąāļšāļĢāļąāļš traffic āļˆāļēāļ internet
  • certbot-init.sh: Script āļŠāļģāļŦāļĢāļąāļšāļ‚āļ­āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ SSL āļ„āļĢāļąāđ‰āļ‡āđāļĢāļāļˆāļēāļ Let’s Encrypt
  • certbot-renew.sh: Script āļŠāļģāļŦāļĢāļąāļšāļ•āđˆāļ­āļ­āļēāļĒāļļāđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ SSL āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī

āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļ•āļīāļ”āļ•āļąāđ‰āļ‡

1ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 1: āļŠāļĢāđ‰āļēāļ‡ Dockerfile

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ Dockerfile āđ€āļžāļ·āđˆāļ­ build Docker image āļ‚āļ­āļ‡ web application (React)

# Build Stage - āļŠāļĢāđ‰āļēāļ‡ application
FROM node:18-alpine as build
# āļ•āļīāļ”āļ•āļąāđ‰āļ‡ pnpm package manager
RUN npm install -g pnpm
WORKDIR /app
# āļāļģāļŦāļ™āļ” environment variable āļŠāļģāļŦāļĢāļąāļš API URL
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL:-http://localhost/api}
# Copy dependency files āđāļĨāļ°āļ•āļīāļ”āļ•āļąāđ‰āļ‡ packages
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# Copy source code āđāļĨāļ° build application
COPY . .
RUN pnpm build
# Production Stage - āļŠāļĢāđ‰āļēāļ‡ production container
FROM nginx:alpine
# Copy built files āđ„āļ›āļĒāļąāļ‡ nginx directory
COPY --from=build /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY .deployment/nginx/web-app.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ:

  • Multi-stage build: āđāļĒāļ build stage āđāļĨāļ° production stage āđ€āļžāļ·āđˆāļ­āļĨāļ”āļ‚āļ™āļēāļ” image
  • VITE_API_URL: āļ•āļąāļ§āđāļ›āļĢāļŠāļģāļŦāļĢāļąāļšāļāļģāļŦāļ™āļ” URL āļ‚āļ­āļ‡ API backend
  • nginx:alpine : āđƒāļŠāđ‰ Nginx āļšāļ™ Alpine Linux āđ€āļžāļ·āđˆāļ­āļ‚āļ™āļēāļ”āđ€āļĨāđ‡āļāđāļĨāļ°āļĢāļ§āļ”āđ€āļĢāđ‡āļ§

2ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 2: āļŠāļĢāđ‰āļēāļ‡ Docker Compose Configuration

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ docker-compose.yml āđ€āļžāļ·āđˆāļ­āļāļģāļŦāļ™āļ”āļ„āđˆāļē services āļ—āļąāđ‰āļ‡āļŦāļĄāļ”:

version: '3.8'
services:
# Main Nginx Server - āļĢāļąāļš traffic āļˆāļēāļ internet
nginx:
image: nginx:latest
container_name: web-app-nginx
ports:
- '80:80' # HTTP
- '443:443' # HTTPS
volumes:
- ./.deployment/nginx/web-app-nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./.deployment/ssl:/etc/nginx/ssl:ro
- certbot-webroot:/var/www/certbot:ro
networks:
- web-app-network
restart: unless-stopped
depends_on:
- web-app
- certbot
# Web Application Container
web-app:
image: ${IMAGE_TAG:-your-registry/web-app:latest}
container_name: web-app
networks:
- web-app-network
environment:
- VITE_API_URL=${VITE_API_URL}
restart: unless-stopped
depends_on:
- certbot
# SSL Certificate Management
certbot:
image: certbot/certbot:latest
container_name: web-app-certbot
volumes:
- ./ssl:/etc/letsencrypt
- certbot-webroot:/var/www/certbot
- ./certbot-logs:/var/log/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done;'"
networks:
- web-app-network
restart: unless-stopped
volumes:
certbot-webroot:
driver: local
networks:
web-app-network:
driver: bridge

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ Services:

  • nginx: Main reverse proxy server āļ—āļĩāđˆāļĢāļąāļš traffic āļˆāļēāļ internet
  • web-app: Container āļ—āļĩāđˆ run React application
  • certbot: āļˆāļąāļ”āļāļēāļĢāđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ SSL āđāļĨāļ°āļ•āđˆāļ­āļ­āļēāļĒāļļāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļ—āļļāļ 12 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡āļœāđˆāļēāļ™ cron job

3ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 3: āļāļģāļŦāļ™āļ”āļ„āđˆāļē Nginx āļŠāļģāļŦāļĢāļąāļš Web App Container

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ .deployment/nginx/web-app.conf:

server {
listen 80;
server_name _;
# āļāļģāļŦāļ™āļ”āļ•āļģāđāļŦāļ™āđˆāļ‡āđ„āļŸāļĨāđŒ static
root /usr/share/nginx/html;
index index.html index.htm;
# āļāļģāļŦāļ™āļ”āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ routing āļŠāļģāļŦāļĢāļąāļš SPA
location / {
# āļžāļĒāļēāļĒāļēāļĄāļŦāļēāđ„āļŸāļĨāđŒāļ•āļēāļĄāļĨāļģāļ”āļąāļš āļ–āđ‰āļēāđ„āļĄāđˆāļĄāļĩāđƒāļŦāđ‰ fallback āđ„āļ›āļ—āļĩāđˆ index.html
try_files $uri $uri/ /index.html;
}
}

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ:

  • try_files: āļŠāļģāļ„āļąāļāļŠāļģāļŦāļĢāļąāļš Single Page Application āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰ routing āļ‚āļ­āļ‡ React āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡
  • server_name _: āļĢāļąāļš request āļˆāļēāļāļ—āļļāļ hostname

4ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 4: āļāļģāļŦāļ™āļ”āļ„āđˆāļē Nginx āļŦāļĨāļąāļāļžāļĢāđ‰āļ­āļĄ SSL

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ .deployment/nginx/web-app-nginx.conf:

# HTTP Server - Redirect āđ„āļ› HTTPS
server {
listen 80;
server_name your-domain.com;
# āđƒāļŦāđ‰ Certbot āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš verification
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirect HTTP traffic āđ„āļ› HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS Server - Main Application
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Certificate Configuration
ssl_certificate /etc/nginx/ssl/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/your-domain.com/privkey.pem;
# SSL Security Settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# Proxy Headers āļŠāļģāļŦāļĢāļąāļš backend communication
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;
# Forward requests āđ„āļ›āļĒāļąāļ‡ web-app container
location / {
proxy_pass http://web-app;
}
# Error Page Handling
error_page 404 /index.html;
error_page 500 502 503 504 /index.html;
# Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļē:

  • HTTP to HTTPS redirect: āļšāļąāļ‡āļ„āļąāļšāđƒāļŦāđ‰āđƒāļŠāđ‰ HTTPS āđ€āļŠāļĄāļ­
  • SSL Security: āļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļ•āļēāļĄ best practices
  • Proxy Pass: āļŠāđˆāļ‡ request āđ„āļ›āļĒāļąāļ‡ web-app container
  • Security Headers: āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāđƒāļŦāđ‰āđ€āļ§āđ‡āļšāđ„āļ‹āļ•āđŒ

5ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 5: āļŠāļĢāđ‰āļēāļ‡ Script āļŠāļģāļŦāļĢāļąāļšāļ‚āļ­ SSL Certificate āļ„āļĢāļąāđ‰āļ‡āđāļĢāļ

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ .deployment/certbot-init.sh:

#!/bin/sh
# āļĢāļąāļš parameters āļˆāļēāļ command line
DOMAIN=$1
EMAIL=$2
# ✅ āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāļĄāļĩ parameters āļ„āļĢāļšāļŦāļĢāļ·āļ­āđ„āļĄāđˆ
if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then
echo "Usage: ./certbot-init.sh <domain> <email>"
echo "Example: ./certbot-init.sh example.com [email protected]"
exit 1
fi
echo "🔐 Obtaining SSL certificate for $DOMAIN..."
echo "📧 Notification email: $EMAIL"
echo "⚠ïļ Make sure to stop any services using port 80 first"
echo " Run: docker compose down"
echo ""
# 🚀 āļĢāļąāļ™ Certbot āđ€āļžāļ·āđˆāļ­āļ‚āļ­āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ SSL
docker run -it --rm --name certbot \
-v "$(pwd)/ssl:/etc/letsencrypt" \
-v "$(pwd)/ssl-var:/var/lib/letsencrypt" \
-p 80:80 \
certbot/certbot certonly \
--standalone \
--email $EMAIL \
--agree-tos \
--no-eff-email \
-d $DOMAIN
# ✅ āļ•āļĢāļ§āļˆāļŠāļ­āļšāļœāļĨāļāļēāļĢāļ—āļģāļ‡āļēāļ™
if [ $? -eq 0 ]; then
echo "✅ SSL certificate obtained successfully!"
echo "📁 Certificate location: ./ssl/live/$DOMAIN/"
echo ""
echo "🚀 Now you can start the services:"
echo " docker compose up -d"
else
echo "❌ Failed to obtain SSL certificate"
echo "ðŸ’Ą Make sure:"
echo " - Domain $DOMAIN points to this server"
echo " - Port 80 is accessible from internet"
echo " - No other service is using port 80"
exit 1
fi

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ Script:

  • standalone mode: āđƒāļŠāđ‰ Certbot āđƒāļ™āđ‚āļŦāļĄāļ”āļ—āļĩāđˆāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļžāļķāđˆāļ‡ web server āļ­āļ·āđˆāļ™
  • port 80: Certbot āļ•āđ‰āļ­āļ‡āļāļēāļĢāđƒāļŠāđ‰ port 80 āđ€āļžāļ·āđˆāļ­ verify domain ownership
  • volume mapping: āđ€āļāđ‡āļšāđƒāļšāļĢāļąāļšāļĢāļ­āļ‡āđƒāļ™āđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒ local āđ€āļžāļ·āđˆāļ­āđƒāļŠāđ‰āļ‡āļēāļ™āļ•āđˆāļ­āđ„āļ›

6ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 6: āļŠāļĢāđ‰āļēāļ‡ Script āļŠāļģāļŦāļĢāļąāļšāļ•āđˆāļ­āļ­āļēāļĒāļļ SSL Certificate (āļāļĢāļ“āļĩ Manual)

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ .deployment/certbot-renew.sh:

#!/bin/sh
echo "🔄 Manually renewing SSL certificates..."
# 🔄 āļŠāļąāđˆāļ‡āļ•āđˆāļ­āļ­āļēāļĒāļļāđƒāļšāļĢāļąāļšāļĢāļ­āļ‡ SSL
docker compose run --rm certbot renew
# ✅ āļ•āļĢāļ§āļˆāļŠāļ­āļšāļœāļĨāļāļēāļĢāļ—āļģāļ‡āļēāļ™
if [ $? -eq 0 ]; then
echo "✅ Certificate renewal successful!"
echo "🔄 Restarting nginx to apply changes..."
# āļĢāļĩāļŠāļ•āļēāļĢāđŒāļ— nginx āđ€āļžāļ·āđˆāļ­āđƒāļŠāđ‰āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡āđƒāļŦāļĄāđˆ
docker compose restart bacc-web-ui
else
echo "❌ Certificate renewal failed"
exit 1
fi

āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļ:

  • Certbot āļˆāļ°āļ•āđˆāļ­āļ­āļēāļĒāļļāđƒāļŦāđ‰āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄ
  • Script āļ™āļĩāđ‰āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļ•āđˆāļ­āļ­āļēāļĒāļļāļ”āđ‰āļ§āļĒāļ•āļ™āđ€āļ­āļ‡ (manual renewal)

7ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 7: āđ€āļ•āļĢāļĩāļĒāļĄāļ„āļ§āļēāļĄāļžāļĢāđ‰āļ­āļĄāļŠāļģāļŦāļĢāļąāļš GitLab CI/CD

āļāđˆāļ­āļ™āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ .gitlab-ci.yml āļ•āđ‰āļ­āļ‡āđ€āļ•āļĢāļĩāļĒāļĄāļāļēāļĢāļ”āļąāļ‡āļ™āļĩāđ‰:

āļ•āļąāļ§āđāļ›āļĢ CI/CD āđƒāļ™ GitLab (Settings > CI/CD > Variables):

  • CI_REGISTRY_USER: Username āļŠāļģāļŦāļĢāļąāļš Docker Registry
  • CI_REGISTRY_PASSWORD: Password āļŠāļģāļŦāļĢāļąāļš Docker Registry
  • SSH_PRIVATE_KEY: Private Key āļŠāļģāļŦāļĢāļąāļšāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­ server
  • DEPLOY_HOST: IP Address āļŦāļĢāļ·āļ­ hostname āļ‚āļ­āļ‡ server
  • DEPLOY_USER: Username āļŠāļģāļŦāļĢāļąāļš SSH āđ€āļ‚āđ‰āļē server
  • DEPLOY_PATH: Path āļšāļ™ server āļŠāļģāļŦāļĢāļąāļš deploy app

āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ SSH Key:

Terminal window
# āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āđ€āļ‚āđ‰āļē server
ssh DEPLOY_USER@DEPLOYHOSE
# āđ€āļ‚āđ‰āļēāđ„āļ›āļ—āļĩāđˆ .ssh
cd .ssh
# run
ssh-keygen
#1 Copy public key āđ„āļ›āđƒāļŠāđˆāđƒāļ™ authorized_keys
#2 Copy private key āđ„āļ›āđƒāļŠāđˆāđƒāļ™ GitLab CI/CD Variables

āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ .gitlab-ci.yml:

stages:
- build
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: '/certs'
DOCKER_IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
DOCKER_IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
# ðŸ”Ļ Build Stage - āļŠāļĢāđ‰āļēāļ‡ Docker Image
build:docker:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- |
echo "Building Docker image: $DOCKER_IMAGE_TAG"
docker build --tag $DOCKER_IMAGE_TAG --tag $DOCKER_IMAGE_LATEST .
echo "Pushing Docker image..."
docker push $DOCKER_IMAGE_TAG
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
echo "Pushing latest tag..."
docker push $DOCKER_IMAGE_LATEST
fi
after_script:
- docker logout
only:
- main
- tags
# 🚀 Deploy Stage - Deploy āđ„āļ›āļĒāļąāļ‡ Production
deploy:production:
stage: deploy
image: docker:24.0.5
services:
- docker:24.0.5-dind
environment:
name: production
url: $PRODUCTION_URL
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- |
# āļ•āļīāļ”āļ•āļąāđ‰āļ‡ SSH client
apk add --no-cache openssh-client
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts
script:
- |
echo "Deploying $DOCKER_IMAGE_TAG to production..."
echo "Image: $DOCKER_IMAGE_TAG"
# Copy docker-compose.yml āđ„āļ›āļĒāļąāļ‡ server
echo "Copying docker-compose.yml to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH..."
scp docker-compose.yml $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/docker-compose.yml
# Copy .deployment directory āđ„āļ›āļĒāļąāļ‡ server
if [ -d ".deployment" ]; then
echo "Copying .deployment directory to $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.deployment..."
ssh $DEPLOY_USER@$DEPLOY_HOST "mkdir -p $DEPLOY_PATH/.deployment"
scp -r .deployment/* $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.deployment/
fi
# āļ•āļąāđ‰āļ‡āļ„āđˆāļē permissions āļŠāļģāļŦāļĢāļąāļšāđ„āļŸāļĨāđŒ deployment
echo "Setting permissions for .deployment files..."
ssh $DEPLOY_USER@$DEPLOY_HOST "
cd $DEPLOY_PATH && \
chmod +x .deployment/certbot-init.sh .deployment/certbot-renew.sh && \
for file in .deployment/certbot-init.sh .deployment/nginx/web-app-nginx.conf .deployment/nginx/web-app.conf; do
if [ ! -f \"\$file\" ]; then
echo \"ERROR: \$file is not a regular file!\"
exit 1
fi
done && \
echo '✓ All .deployment files verified as regular files'
"
# Deploy application āļšāļ™ server
ssh $DEPLOY_USER@$DEPLOY_HOST "
cd $DEPLOY_PATH && \
echo 'Logging into Docker registry...' && \
echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY && \
export IMAGE_TAG=$DOCKER_IMAGE_TAG && \
export DOMAIN=${DOMAIN:-localhost} && \
echo 'Pulling Docker images...' && \
docker compose -f docker-compose.yml pull && \
echo 'Starting containers...' && \
docker compose -f docker-compose.yml up -d && \
docker compose -f docker-compose.yml restart nginx && \
docker compose -f docker-compose.yml ps && \
docker logout $CI_REGISTRY
"
echo "✓ Deployment completed successfully"
after_script:
- docker logout
only:
- main
- tags
when: manual # āļ•āđ‰āļ­āļ‡āļāļ”āļ›āļļāđˆāļĄ Deploy āļ”āđ‰āļ§āļĒāļ•āļ™āđ€āļ­āļ‡

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ Pipeline:

  • Build Stage: āļŠāļĢāđ‰āļēāļ‡ Docker image āđāļĨāļ° push āđ„āļ›āļĒāļąāļ‡ registry
  • Deploy Stage: Download image āđāļĨāļ°āļĢāļąāļ™ containers āļšāļ™ production server
  • Manual Deployment: āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢ deploy āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāđ‚āļ”āļĒāđ„āļĄāđˆāļ•āļąāđ‰āļ‡āđƒāļˆ

8ïļâƒĢ āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆ 8: Initial Deployment

āđ€āļĄāļ·āđˆāļ­āļ—āļļāļāļ­āļĒāđˆāļēāļ‡āđ€āļ•āļĢāļĩāļĒāļĄāļžāļĢāđ‰āļ­āļĄāđāļĨāđ‰āļ§ āđƒāļŦāđ‰āļ—āļģāļāļēāļĢ ðŸš€ deploy āļ„āļĢāļąāđ‰āļ‡āđāļĢāļāļ”āļąāļ‡āļ™āļĩāđ‰:

āđ€āļ•āļĢāļĩāļĒāļĄ Server Environment:

Terminal window
# āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āđ€āļ‚āđ‰āļē server
ssh DEPLOY_USER@DEPLOYHOSE
# āļŠāļĢāđ‰āļēāļ‡āđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒāļŠāļģāļŦāļĢāļąāļš application /var/www/web-app
mkdir -p DEPLOY_PATH

āļ‚āļ­ SSL Certificate āļ„āļĢāļąāđ‰āļ‡āđāļĢāļ āļ—āļģāđ„āļ”āđ‰ 2 āđāļšāļš:

āđāļšāļšāļ—āļĩāđˆ 1

  1. āļ™āļģāđ„āļŸāļĨāđŒ .deployment āļĄāļēāļ—āļĩāđˆ server DEPLOY_PATH āļāđˆāļ­āļ™ (āļœāđˆāļēāļ™ scp āļŦāļĢāļ·āļ­ git clone)

  2. āļ•āļąāđ‰āļ‡āļ„āđˆāļē permissions:

    Terminal window
    chmod +x .deployment/certbot-init.sh
  3. āļĢāļąāļ™ script certbot:

    Terminal window
    ./certbot-init.sh yourdomain.com [email protected]
  4. āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒ ssl āđāļĨāļ° ssl-var āļ–āļđāļāļŠāļĢāđ‰āļēāļ‡āļ‚āļķāđ‰āļ™āļŦāļĢāļ·āļ­āđ„āļĄāđˆ:

    Terminal window
    ls
  5. āļĢāļąāļ™ pipeline deploy:production āļ—āļĩāđˆ GitLab

  6. āđ€āļŠāļĢāđ‡āļˆāļŠāļīāđ‰āļ™ âœ…

āđāļšāļšāļ—āļĩāđˆ 2 (āđƒāļŠāđ‰ GitLab CI/CD pipeline)

  1. āļĢāļąāļ™ pipeline deploy:production āļ—āļĩāđˆ GitLab āļāđˆāļ­āļ™
  2. āđ€āļ‚āđ‰āļēāđ„āļ›āđƒāļ™ server:
    Terminal window
    cd DEPLOY_PATH/.deployment
  3. āļĢāļąāļ™ script certbot:
    Terminal window
    ./certbot-init.sh yourdomain.com [email protected]
  4. āļĢāļąāļ™ pipeline deploy:production āļ—āļĩāđˆ GitLab āļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡
  5. āđ€āļŠāļĢāđ‡āļˆāļŠāļīāđ‰āļ™ âœ…

āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļ: āļāļēāļĢāļ‚āļ­ SSL Certificate āđāļšāļšāļ—āļĩāđˆ 2 āļˆāļ°āđ„āļ”āđ‰āļĢāļąāļšāļŠāļīāļ—āļ˜āļīāđŒ execute āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļˆāļēāļ CI/CD

✅ Deploy āļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāđ‰āļ§āļ§āļ§āļ§āļ§ âĪïļðŸŧ

  • āđ€āļ‚āđ‰āļē website

⚠ïļ āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļāļŠāļģāļ„āļąāļ:

āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļāđˆāļ­āļ™ Deploy:

  • ✅ Server Setup:

    Terminal window
    # āļ­āļąāļžāđ€āļ”āļ—āļĢāļ°āļšāļšāđƒāļŦāđ‰āđ€āļ›āđ‡āļ™āđ€āļ§āļ­āļĢāđŒāļŠāļąāđˆāļ™āļĨāđˆāļēāļŠāļļāļ” on Debian
    sudo apt update
    sudo apt upgrade -y
    # āļ•āļīāļ”āļ•āļąāđ‰āļ‡ packages āļžāļ·āđ‰āļ™āļāļēāļ™āļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™
    sudo apt install -y curl wget git vim nano htop unzip software-properties-common apt-transport-https ca-certificates gnupg lsb-release
    # āļĨāļš Docker āđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™āđ€āļāđˆāļē (āļ–āđ‰āļēāļĄāļĩ)
    sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1)
    # āđ€āļžāļīāđˆāļĄ GPG Key āļ‚āļ­āļ‡ Docker
    sudo mkdir -p /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    sudo chmod a+r /etc/apt/keyrings/docker.gpg
    # āļ­āļąāļ›āđ€āļ”āļ• index
    sudo apt update
    # āļ•āļīāļ”āļ•āļąāđ‰āļ‡āđāļžāđ‡āļāđ€āļāļˆ Docker āļĨāđˆāļēāļŠāļļāļ”
    sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    # āļ”āļđāđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™
    docker --version
    docker compose version
    # start docker
    sudo systemctl start docker
  • ✅ Domain Name: āļ•āđ‰āļ­āļ‡āļŠāļĩāđ‰ A record āđ„āļ›āļĒāļąāļ‡ IP āļ‚āļ­āļ‡ server

  • ✅ Port Access: āđ€āļ›āļīāļ” port 80 āđāļĨāļ° 443 āđƒāļ™ firewall

  • ✅ Docker Installation: āļ•āļīāļ”āļ•āļąāđ‰āļ‡ Docker āđāļĨāļ° Docker Compose āļšāļ™ server (āļ”āļđāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ”āđ‰āļēāļ™āļšāļ™)

  • ✅ SSL Certificate: āļ‚āļ­āđƒāļšāļĢāļąāļšāļĢāļ­āļ‡āļāđˆāļ­āļ™ enable HTTPS

āļ„āļģāļŠāļąāđˆāļ‡āļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āđ‚āļĒāļŠāļ™āđŒ

Terminal window
# āļ”āļđāđ„āļŸāļĨāđŒāđƒāļ™ containers
docker compose exec -it nginx sh
docker compose exec -it web-app sh
docker compose exec -it certbot sh
# āļ”āļđ logs āļ‚āļ­āļ‡ containers
docker compose logs -f nginx
docker compose logs -f web-app
docker compose logs -f certbot
docker compose logs
# āļĢāļĩāļŠāļ•āļēāļĢāđŒāļ— specific container
docker compose restart nginx
docker compose restart web-app
# āļ­āļąāļžāđ€āļ”āļ— containers āļ”āđ‰āļ§āļĒ image āđƒāļŦāļĄāđˆ
docker compose pull
docker compose up -d
# āļ•āļĢāļ§āļˆāļŠāļ­āļš SSL certificate
openssl x509 -in ssl/live/your-domain.com/fullchain.pem -text -noout
# āļ—āļ”āļŠāļ­āļšāļāļēāļĢāļ•āđˆāļ­āļ­āļēāļĒāļļ SSL (dry run)
docker compose run --rm certbot renew --dry-run

āļāļēāļĢ Monitor āđāļĨāļ° Maintenance

1. Log Monitoring

Terminal window
# āļ•āļīāļ”āļ•āļēāļĄ logs āđāļšāļš real-time
docker compose logs -f
# āļ”āļđ logs āļ‚āļ­āļ‡ nginx access
docker compose exec nginx tail -f /var/log/nginx/access.log
# āļ•āļĢāļ§āļˆāļŠāļ­āļš disk usage
df -h
du -sh ssl/

2. SSL Certificate Management

Terminal window
# āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āļąāļ™āļŦāļĄāļ”āļ­āļēāļĒāļļ
docker compose run --rm certbot certificates
# āļ•āđˆāļ­āļ­āļēāļĒāļļāļ”āđ‰āļ§āļĒāļ•āļ™āđ€āļ­āļ‡
./.deployment/certbot-renew.sh
# āļ—āļ”āļŠāļ­āļšāļāļēāļĢāļ•āđˆāļ­āļ­āļēāļĒāļļ
docker compose run --rm certbot renew --dry-run

3. Backup Strategy

Terminal window
# āļŠāļģāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļģāļ„āļąāļ
tar -czf backup-$(date +%Y%m%d).tar.gz ssl/ docker-compose.yml .deployment/
# Restore
tar -xzf backup-20241126.tar.gz

Document

āļāļēāļĢāļ•āļīāļ”āļ•āđˆāļ­āđāļĨāļ°āļŠāļ™āļąāļšāļŠāļ™āļļāļ™

āļŦāļēāļāļžāļšāļ›āļąāļāļŦāļēāđƒāļ™āļāļēāļĢ deploy āļŠāļēāļĄāļēāļĢāļ–āļ•āļĢāļ§āļˆāļŠāļ­āļšāđ„āļ”āđ‰āļˆāļēāļ:

  1. Container Logs: docker compose logs
  2. Nginx Logs: āđƒāļ™ container āļŦāļĢāļ·āļ­ host system
  3. SSL Status: docker compose run --rm certbot certificates
  4. Network Connectivity: curl -I https://your-domain.com

āļ§āļąāļ™āļ—āļĩāđˆāļ­āļąāļžāđ€āļ”āļ—āļĨāđˆāļēāļŠāļļāļ”: 26 āļžāļĪāļĻāļˆāļīāļāļēāļĒāļ™ 2025