VPS-এ Application Deploy — PHP, Node, Python

লেসন ২২ / ৩৮

📥 Git-based Deploy

# SSH key for GitHub
ssh-keygen -t ed25519 -f ~/.ssh/github_deploy
# Add ~/.ssh/github_deploy.pub to GitHub Deploy Keys

# Clone
cd /var/www
sudo git clone git@github.com:user/myapp.git
sudo chown -R deploy:www-data myapp

🐘 Laravel Deploy

cd /var/www/myapp

# Composer install
composer install --no-dev --optimize-autoloader

# .env setup
cp .env.example .env
nano .env
# DB_CONNECTION=mysql
# DB_DATABASE=myapp
# APP_ENV=production
# APP_DEBUG=false
# APP_URL=https://example.com

php artisan key:generate
php artisan migrate --force
php artisan storage:link

# Cache for production
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Queue worker (systemd service)
# /etc/systemd/system/laravel-queue.service
[Service]
User=deploy
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3
Restart=always

# Schedule
* * * * * cd /var/www/myapp && php artisan schedule:run >/dev/null 2>&1

# Permission fix
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache

🟢 Node.js Deploy with PM2

cd /var/www/api
npm ci --production
# OR pnpm install --prod

# .env file
nano .env

# PM2 ecosystem
nano ecosystem.config.js

module.exports = {
  apps: [{
    name: "api",
    script: "./server.js",
    instances: "max",        // = CPU count
    exec_mode: "cluster",
    env: {
      NODE_ENV: "production",
      PORT: 3000
    },
    error_file: "./logs/err.log",
    out_file: "./logs/out.log",
    max_memory_restart: "500M"
  }]
};

pm2 start ecosystem.config.js
pm2 save
pm2 startup    # follow output, paste cmd

pm2 list
pm2 logs api
pm2 monit
pm2 reload api    # zero-downtime restart

🐍 Django Deploy

cd /var/www/django-app
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt gunicorn

# .env
DJANGO_SECRET_KEY=...
DEBUG=False
ALLOWED_HOSTS=example.com

python manage.py migrate
python manage.py collectstatic --noinput

# Gunicorn systemd service
[Service]
User=deploy
WorkingDirectory=/var/www/django-app
ExecStart=/var/www/django-app/venv/bin/gunicorn \
    --workers 4 --bind unix:/run/gunicorn.sock \
    myapp.wsgi:application
Restart=always

# Nginx proxy
location / {
    proxy_pass http://unix:/run/gunicorn.sock;
    include proxy_params;
}

🔐 .env Security

chmod 600 .env
chown deploy:deploy .env

# .gitignore
.env
.env.production

# Nginx block access
location ~ /\.env {
    deny all;
}

🚀 Zero-Downtime Deploy Script

#!/bin/bash
set -euo pipefail

cd /var/www/myapp
php artisan down                # maintenance mode

git fetch origin main
git reset --hard origin/main

composer install --no-dev --optimize-autoloader
php artisan migrate --force

php artisan config:cache
php artisan route:cache
php artisan view:cache

sudo systemctl reload php8.2-fpm

php artisan up                  # back online

# Restart queue workers
sudo systemctl restart laravel-queue

🎯 Atomic Symlink Deploy (advanced)

# Releases pattern (Capistrano-style)
/var/www/myapp/
├── releases/
│   ├── 20240101-120000/   # old
│   └── 20240102-100000/   # new
├── shared/
│   ├── .env
│   └── storage/
└── current → releases/20240102-100000  # symlink

# Deploy = create new release dir, swap symlink atomically
ln -sfn /var/www/myapp/releases/$NEW_VERSION /var/www/myapp/current

🎓 মূল কথা ⭐

Git pull + cache + reload = 5 min deploy। PM2 cluster = zero-downtime। .env permission 600 mandatory। Symlink-based deploy = rollback 1 sec।