Skip to main content
ClaudeWave
Install in Claude Code
Copy
git clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-ops-deployment && cp -r /tmp/frappe-ops-deployment/skills/source/ops/frappe-ops-deployment ~/.claude/skills/frappe-ops-deployment
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Production Deployment

Deploy Frappe/ERPNext to production using either traditional (bench + Nginx + Supervisor) or Docker (frappe_docker + Compose). Frappe officially recommends Docker for new deployments.

## Quick Reference

```bash
# Traditional production setup (one command)
sudo bench setup production [frappe-user]

# What it configures:
# 1. Supervisor — process management (gunicorn, workers, Redis, socketio)
# 2. Nginx — reverse proxy, static files, websocket proxy
# 3. Sudoers — allows frappe-user to restart services

# Individual setup commands
bench setup supervisor          # Generate supervisor config
bench setup nginx               # Generate nginx config
bench setup sudoers $(whoami)   # Allow service restarts without password

# Symlink configs into system directories
sudo ln -s $(pwd)/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.conf
sudo ln -s $(pwd)/config/nginx.conf /etc/nginx/conf.d/frappe-bench.conf

# SSL setup
sudo -H bench setup lets-encrypt [site-name]
sudo -H bench setup lets-encrypt [site-name] --custom-domain [domain]

# DNS multitenancy (multiple sites on port 80/443)
bench config dns_multitenant on
bench setup nginx
sudo service nginx reload
```

---

## Deployment Decision Tree

```
Which deployment method?
|
+-- New server, minimal ops experience?
|   +-- Docker (frappe_docker) — recommended by Frappe
|
+-- Existing server with bench already installed?
|   +-- Traditional (bench setup production)
|
+-- Need custom Frappe apps or complex build?
|   +-- Docker with custom image build
|
+-- Cloud hosting (AWS/GCP/Azure)?
|   +-- Docker on VM or Kubernetes
|   +-- OR Frappe Cloud (managed)
|
+-- Single site or multi-site?
|   +-- Single site: standard setup
|   +-- Multi-site: DNS multitenancy required
```

---

## Traditional Deployment

### Process Architecture

```
Internet → Nginx (port 80/443)
               |
               +-- Static files served directly
               +-- /api, /app → Gunicorn (port 8000)
               +-- /socket.io → Node.js socketio (port 9000)

Supervisor manages:
  - frappe-bench-web (gunicorn)
  - frappe-bench-socketio (node)
  - frappe-bench-worker-short
  - frappe-bench-worker-default
  - frappe-bench-worker-long
  - frappe-bench-redis-cache
  - frappe-bench-redis-queue
  - frappe-bench-schedule (scheduler)
```

### Step-by-Step Setup

```bash
# 1. Install bench (as non-root user)
sudo pip3 install frappe-bench
bench init frappe-bench --frappe-branch version-15
cd frappe-bench

# 2. Create site
bench new-site mysite.example.com
bench --site mysite.example.com install-app erpnext

# 3. Production setup (configures nginx + supervisor + sudoers)
sudo bench setup production $(whoami)

# 4. Verify processes are running
sudo supervisorctl status

# 5. Verify nginx config
sudo nginx -t && sudo systemctl reload nginx
```

### Nginx Configuration

`bench setup nginx` generates `config/nginx.conf` with:
- Server block per site (DNS multitenancy)
- Proxy to gunicorn on port 8000
- WebSocket proxy to socketio on port 9000
- Static file serving from `sites/` directory
- Client max body size (default varies by version)

**ALWAYS disable default nginx site** to avoid port 80 conflicts:
```bash
sudo rm /etc/nginx/sites-enabled/default
# OR disable: sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
```

### Supervisor Configuration

`bench setup supervisor` generates `config/supervisor.conf` with:
- `--skip-redis` flag to skip Redis if managed externally

For CentOS/RHEL: use `.ini` extension instead of `.conf` for supervisor configs.

---

## SSL / HTTPS

### Let's Encrypt (Recommended)

```bash
# Automated setup with cron renewal
sudo -H bench setup lets-encrypt mysite.example.com

# For custom domain (site name differs from domain)
sudo -H bench setup lets-encrypt mysite.example.com --custom-domain www.example.com

# Manual renewal
sudo bench renew-lets-encrypt
```

**Prerequisites**:
- DNS multitenancy enabled (`bench config dns_multitenant on`)
- Domain resolves to server IP
- Port 80 open for ACME challenge
- Root/sudo access

**Certificate locations**: `/etc/letsencrypt/live/example.com/`
- `fullchain.pem` — certificate + chain
- `privkey.pem` — private key

Certificates expire every 90 days. The setup command adds a monthly cron for renewal.

### Custom SSL Certificate

```bash
# 1. Place certificate files
sudo mkdir -p /etc/nginx/conf.d/ssl
sudo cp certificate.crt /etc/nginx/conf.d/ssl/
sudo cp private.key /etc/nginx/conf.d/ssl/
sudo chmod 600 /etc/nginx/conf.d/ssl/private.key

# 2. Configure site
bench set-config ssl_certificate "/etc/nginx/conf.d/ssl/certificate.crt"
bench set-config ssl_certificate_key "/etc/nginx/conf.d/ssl/private.key"

# 3. Regenerate and reload
bench setup nginx
sudo systemctl reload nginx
```

All HTTP traffic is automatically redirected to HTTPS after SSL is configured.

---

## DNS Multitenancy (Multi-Site)

```bash
# Enable DNS-based site routing
bench config dns_multitenant on

# Create sites with domain names
bench new-site site1.example.com
bench new-site site2.example.com

# Regenerate nginx (creates server blocks per site)
bench setup nginx
sudo systemctl reload nginx
```

ALWAYS use the actual domain as the site name. Nginx routes requests to the correct site based on the `Host` header.

---

## Docker Deployment

### Architecture (frappe_docker)

```
Docker Compose Services:
  - configurator  — initializes DB/Redis config (runs once)
  - backend       — Frappe/ERPNext application server (gunicorn)
  - frontend      — Nginx reverse proxy
  - websocket     — Node.js Socket.IO server
  - queue-short   — RQ worker for short jobs
  - queue-long    — RQ worker for long jobs
  - (external)    — MariaDB/PostgreSQL + Redis (separate containers or managed)

Shared Volume:
  - sites:/home/frappe/frappe-bench/sites (persistent data)
```

### Production Docker Compose

```bash
# Clone frappe_docker
git clone https://github.com/frappe/frappe_docker.git
cd frappe_docker

#