frappe_docker/production
2025-11-13 15:46:32 +05:30
..
scripts fix: update script permissions to make them executable 2025-11-13 15:46:32 +05:30
mariadb.env.example Add production environment configuration and deployment scripts for ERPNext 2025-11-13 15:32:30 +05:30
production.env.example Add production environment configuration and deployment scripts for ERPNext 2025-11-13 15:32:30 +05:30
README.md Add production environment configuration and deployment scripts for ERPNext 2025-11-13 15:32:30 +05:30

Production ERPNext Deployment

Complete guide for deploying ERPNext in production using Docker Compose with Traefik and Let's Encrypt SSL.


Overview

This guide provides everything needed to deploy a production-ready ERPNext instance using Docker Compose. The setup includes:

  • ERPNext v15.82.1 - ERP application
  • Frappe Framework v15.82.1 - Application framework
  • Traefik v2.11 - Reverse proxy with automatic Let's Encrypt SSL
  • MariaDB 11.8 - Database server
  • Redis - Cache, queue, and socketio

Architecture

Three separate Docker Compose projects work together:

  1. Traefik - Reverse proxy, SSL certificates, load balancing
  2. MariaDB - Shared database for all ERPNext sites
  3. ERPNext - Application containers (backend, frontend, workers, scheduler)

Repository Structure

erp-is/
├── production/              # Production deployment (this directory)
│   ├── README.md           # This guide
│   ├── *.env.example       # Configuration templates
│   ├── customizations/     # Brand customizations (CSS, favicon, logo)
│   │   ├── custom.css
│   │   ├── favicon.ico
│   │   └── logo.png
│   ├── scripts/            # Automation scripts
│   │   ├── deploy.sh              # Main deployment (includes setup/regenerate)
│   │   ├── create-site.sh         # Create new site
│   │   ├── backup-site.sh         # Backup automation
│   │   ├── validate-env.sh        # Config validation
│   │   ├── logs.sh                # Log viewer
│   │   └── stop.sh                # Stop services
│   └── production.yaml     # Generated compose file (do not edit)
├── overrides/              # Upstream compose overlays
├── docs/                   # Upstream documentation
└── compose.yaml            # Upstream base configuration

Important: This is a fork of frappe/frappe_docker:

  • Custom files (in production/) are maintained in this fork
  • Infrastructure files (compose.yaml, overrides/) track upstream
  • Container images are pulled from official Frappe Docker registry

Table of Contents


Quick Start

For experienced users:

# 1. Generate secure passwords
openssl rand -base64 32  # DB password
openssl rand -base64 24  # Admin password
htpasswd -nB admin       # Traefik dashboard password

# 2. Configure environment (interactive setup)
./scripts/deploy.sh --setup  # Creates *.env from templates
# Edit all three files with your values

# 3. Validate configuration
./scripts/validate-env.sh    # Checks for errors before deploy

# 4. Deploy all services
./scripts/deploy.sh

# 5. Create your first site
./scripts/create-site.sh erp.example.com

# 6. View logs
./scripts/logs.sh           # Interactive service selection

First time? Follow the complete checklist below.


Prerequisites

System Requirements

  • OS: Ubuntu 20.04/22.04 LTS or Debian 11/12
  • CPU: 2+ cores (4+ recommended)
  • RAM: 4GB minimum (8GB+ recommended)
  • Disk: 50GB+ SSD storage
  • Network: Public IP with ports 80 and 443 open
  • Domain: DNS pointing to your server

Software Requirements

# Docker Engine 20.10+
docker --version

# Docker Compose v2
docker compose version

Pre-Deployment Checklist

1. Verify System

# Check OS version
lsb_release -a

# Check available RAM
free -h

# Check disk space
df -h

# Check server is accessible
ping -c 3 $(hostname -I | awk '{print $1}')

2. Configure DNS

# Verify DNS propagation (wait 24-48h after DNS change)
dig +short erp.example.com
# Should return your server's public IP

3. Configure Firewall

# Allow required ports
sudo ufw allow 22/tcp   # SSH
sudo ufw allow 80/tcp   # HTTP
sudo ufw allow 443/tcp  # HTTPS
sudo ufw enable

# Verify
sudo ufw status

4. Install Docker

# Install Docker Engine
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add user to docker group
sudo usermod -aG docker $USER

# Logout and login again, then test
docker ps

5. Validate Environment

# Run validation script (checks all config files)
./scripts/validate-env.sh

# Should pass all checks before proceeding

Script Usage Guide

All scripts have been optimized for efficiency and include comprehensive help. Every script supports -h or --help.

📋 Script Overview

Script Purpose Key Features
deploy.sh Main deployment --setup, --regenerate, validation
create-site.sh Create ERPNext site Interactive prompts, validation
backup-site.sh Advanced backups Encryption, auto-copy, cleanup
logs.sh View service logs Interactive menu, service selection
stop.sh Stop services Selective stopping, --all option
validate-env.sh Config validation Password strength, cross-validation

🚀 Deploy Script

# Get help
./scripts/deploy.sh --help

# Setup environment files from templates
./scripts/deploy.sh --setup

# Validate and deploy all services
./scripts/deploy.sh

# Regenerate production.yaml only (no deploy)
./scripts/deploy.sh --regenerate

🏗️ Create Site Script

# Get help
./scripts/create-site.sh --help

# Interactive mode
./scripts/create-site.sh

# Direct usage
./scripts/create-site.sh erp.example.com
./scripts/create-site.sh erp.example.com MySecurePass123

💾 Backup Script (Advanced)

# Get help
./scripts/backup-site.sh --help

# Basic backup
./scripts/backup-site.sh erp.example.com

# Advanced backup with files, auto-copy, cleanup
./scripts/backup-site.sh erp.example.com --with-files --auto-copy --cleanup-old

# Encrypted backup
BACKUP_PASSPHRASE='your-secret' ./scripts/backup-site.sh erp.example.com --encrypt --auto-copy

# Automated (environment variables)
AUTO_COPY=1 CLEANUP_OLD=1 ./scripts/backup-site.sh erp.example.com

Backup Features:

  • Encryption: GPG symmetric encryption with AES256
  • Auto-copy: Copy backups from container to host ./backups/
  • Cleanup: Remove old backups (configurable retention)
  • Validation: Verify backup files and sizes
  • Logging: Detailed operation logs in /tmp/

📊 Logs Script

# Get help
./scripts/logs.sh --help

# Interactive menu
./scripts/logs.sh

# Direct service access
./scripts/logs.sh 1          # Backend logs
./scripts/logs.sh backend    # Same as above
./scripts/logs.sh frontend   # Nginx logs
./scripts/logs.sh all        # All services

Available Services:

  1. backend - Gunicorn application server
  2. frontend - Nginx reverse proxy
  3. websocket - Socket.io for real-time features
  4. queue-short - Short-running background jobs
  5. queue-long - Long-running background jobs
  6. scheduler - Cron-like background scheduler
  7. all - All services combined

🛑 Stop Script

# Get help
./scripts/stop.sh --help

# Interactive mode (asks about MariaDB/Traefik)
./scripts/stop.sh

# Stop everything without prompts
./scripts/stop.sh --all

Validation Script

# Get help
./scripts/validate-env.sh --help

# Validate all environment files
./scripts/validate-env.sh

Validation Checks:

  • Required variables present
  • Password strength (16+ characters recommended)
  • Email format validation
  • Cross-file password matching
  • Placeholder detection
  • Weak password detection

Environment Configuration

Three Environment Files Required

  1. production/production.env - ERPNext application config
  2. production/mariadb.env - Database config
  3. production/traefik.env - Reverse proxy config

Step-by-Step Configuration

1. Generate Passwords

# Database password (use in production.env AND mariadb.env)
openssl rand -base64 32

# Admin password (use in production.env)
openssl rand -base64 24

# Traefik dashboard password (use in traefik.env)
htpasswd -nB admin
# Or use: https://hostingcanada.org/htpasswd-generator/

2. Configure production/production.env

./scripts/deploy.sh --setup  # Create from templates
nano production/production.env

Required values:

SITES=erp.example.com                           # Your domain
ERPNEXT_VERSION=v15.82.1                        # ERPNext version
FRAPPE_VERSION=v15.82.1                         # Frappe version
DB_HOST=mariadb-database                        # Database host
DB_PASSWORD=<paste-generated-db-password>       # From step 1
REDIS_CACHE=redis-cache:6379                    # Redis cache
REDIS_QUEUE=redis-queue:6379                    # Redis queue
REDIS_SOCKETIO=redis-socketio:6379              # Redis socketio
LETSENCRYPT_EMAIL=admin@example.com             # For SSL certs
ADMIN_PASSWORD=<paste-generated-admin-password> # From step 1

3. Configure production/mariadb.env

nano production/mariadb.env  # Already created by --setup

Required values:

DB_PASSWORD=<same-as-production.env>  # MUST match production.env

4. Configure production/traefik.env

nano production/traefik.env  # Already created by --setup

Required values:

TRAEFIK_DOMAIN=traefik.example.com    # Dashboard subdomain
EMAIL=admin@example.com               # For SSL certs
HASHED_PASSWORD=<paste-from-htpasswd> # From step 1

5. Secure Files

chmod 600 production/*.env

# Verify they're git-ignored
git status | grep production.env
# Should return nothing

Deployment

Deploy Services

./scripts/deploy.sh

What it does:

  1. Validates environment configuration (./scripts/validate-env.sh)
  2. Deploys Traefik (reverse proxy + SSL)
  3. Deploys MariaDB (database)
  4. Generates ERPNext configuration (production.yaml) from:
    • Base: compose.yaml
    • Redis overlay: overrides/compose.redis.yaml
    • Multi-bench overlay: overrides/compose.multi-bench.yaml
    • SSL overlay: overrides/compose.multi-bench-ssl.yaml
  5. Deploys ERPNext (backend, frontend, workers, scheduler)

Expected output:

[INFO] ERPNext Production Deployment
[INFO] Validating configuration...
✅ Validation Passed
[INFO] Step 1: Deploying Traefik...
✓ Traefik deployed
[INFO] Step 2: Deploying MariaDB...
✓ MariaDB deployed. Waiting 30s for initialization...
[INFO] Step 3: Generating production.yaml...
✓ Generated
[INFO] Step 4: Deploying ERPNext...
✓ ERPNext deployed
[INFO] ✓ Deployment complete!

Create Site

./scripts/create-site.sh erp.example.com

Wait 2-3 minutes for:

  • Database initialization
  • Frappe installation
  • ERPNext installation
  • SSL certificate generation

Verify Deployment

# Check all services are running
docker ps

# Check service health
docker compose -f production/production.yaml ps

# View logs
./scripts/logs.sh

# Follow logs (live)
./scripts/logs.sh -f backend

### Access Your Site

1. Open browser: `https://erp.example.com`
2. Username: `Administrator`
3. Password: Check `production/production.env` → `ADMIN_PASSWORD`

---

## Branding Customization

### Overview

Customize your ERPNext instance with your company's brand identity including favicon, logo, colors, and styling. This section provides **manual procedures** for applying branding changes.

### Step 1: Prepare Customization Assets

#### Create Directory Structure

```bash
mkdir -p production/customizations
cd production/customizations

Prepare Your Files

You'll need:

  • favicon.ico - Browser tab icon (16x16, 32x32, 48x48 px)
  • favicon.png - Modern browser icon (192x192 px recommended)
  • logo.png - Company logo (any size, PNG with transparency recommended)
  • custom.css - Brand colors and styling overrides

Generate Favicon from Logo (Optional)

If you have a logo and need to create favicons:

# Install ImageMagick
sudo apt install imagemagick -y

# Generate multiple sizes
convert your-logo.png -resize 16x16 favicon-16.ico
convert your-logo.png -resize 32x32 favicon-32.ico
convert your-logo.png -resize 48x48 favicon.ico
convert your-logo.png -resize 192x192 favicon.png

# Or use online tool: https://realfavicongenerator.net/

Step 2: Create Custom CSS

Create production/customizations/custom.css:

/* Brand Color Scheme */
:root {
    --primary: #1a365d;        /* Primary brand color (navbar, buttons) */
    --secondary: #2c5282;      /* Secondary brand color */
    --accent: #3182ce;         /* Accent color (links, hover states) */
    --text-on-primary: #ffffff; /* Text color on primary background */
}

/* Apply primary color to navbar */
.navbar {
    background-color: var(--primary) !important;
}

.navbar .navbar-brand,
.navbar .nav-link {
    color: var(--text-on-primary) !important;
}

/* Primary buttons */
.btn-primary {
    background-color: var(--primary) !important;
    border-color: var(--primary) !important;
}

.btn-primary:hover {
    background-color: var(--accent) !important;
    border-color: var(--accent) !important;
}

/* Links */
a {
    color: var(--accent) !important;
}

a:hover {
    color: var(--primary) !important;
}

/* Hide "Powered by ERPNext" footer */
.footer-powered {
    display: none !important;
}

/* Login page branding */
.login-content {
    background-color: #f7fafc;
}

.login-content .card {
    border: 1px solid var(--primary);
}

Customize the colors:

  • Replace #1a365d, #2c5282, #3182ce with your brand colors
  • Use a color picker to get hex codes from your logo

Step 3: Configure Docker Volume Mounts

Add volume mounts to production/production.yaml to inject your customizations.

Option A: Edit production.yaml directly (regenerate will overwrite)

nano production/production.yaml

Find the backend service and add under volumes::

services:
  backend:
    volumes:
      # ... existing volumes ...
      - ./customizations/custom.css:/home/frappe/frappe-bench/sites/assets/custom.css:ro
      - ./customizations/favicon.ico:/home/frappe/frappe-bench/sites/assets/favicon.ico:ro
      - ./customizations/favicon.png:/home/frappe/frappe-bench/sites/assets/favicon.png:ro
      - ./customizations/logo.png:/home/frappe/frappe-bench/sites/assets/logo.png:ro

Also add to frontend service:

  frontend:
    volumes:
      # ... existing volumes ...
      - ./customizations/custom.css:/usr/share/nginx/html/assets/custom.css:ro
      - ./customizations/favicon.ico:/usr/share/nginx/html/assets/favicon.ico:ro
      - ./customizations/favicon.png:/usr/share/nginx/html/assets/favicon.png:ro
      - ./customizations/logo.png:/usr/share/nginx/html/assets/logo.png:ro

Option B: Create compose.custom.yaml overlay (recommended, survives regeneration)

Create production/compose.custom.yaml:

services:
  backend:
    volumes:
      - ./customizations/custom.css:/home/frappe/frappe-bench/sites/assets/custom.css:ro
      - ./customizations/favicon.ico:/home/frappe/frappe-bench/sites/assets/favicon.ico:ro
      - ./customizations/favicon.png:/home/frappe/frappe-bench/sites/assets/favicon.png:ro
      - ./customizations/logo.png:/home/frappe/frappe-bench/sites/assets/logo.png:ro

  frontend:
    volumes:
      - ./customizations/custom.css:/usr/share/nginx/html/assets/custom.css:ro
      - ./customizations/favicon.ico:/usr/share/nginx/html/assets/favicon.ico:ro
      - ./customizations/favicon.png:/usr/share/nginx/html/assets/favicon.png:ro
      - ./customizations/logo.png:/usr/share/nginx/html/assets/logo.png:ro

Then modify scripts/deploy.sh to include this overlay during generation (search for the docker compose command that generates production.yaml).

Step 4: Apply Volume Mounts

# Restart services to mount new files
docker compose -f production/production.yaml restart backend frontend

# Verify files are mounted
docker compose -f production/production.yaml exec backend \
  ls -la /home/frappe/frappe-bench/sites/assets/

# Should show: custom.css, favicon.ico, favicon.png, logo.png

Step 5: Configure Site to Use Custom Assets

Method 1: Via Website Settings (Recommended)

  1. Login to ERPNext: https://erp.example.com

  2. Go to: Setup → Website Settings

  3. Set the following:

    • Favicon: Upload or set path to /assets/favicon.ico
    • Brand HTML: Add custom logo if needed
    • Website Theme: Create custom theme with your colors
    • Custom HTML: Add CSS reference if needed
  4. Go to: Setup → System Settings

    • Upload app icon if needed

Method 2: Via Bench Commands

# Set site config to use custom CSS
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com set-config app_include_css '["/assets/custom.css"]'

# Set favicon
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com set-config app_logo_url '/assets/favicon.png'

# Clear cache and rebuild
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

Method 3: Via site_config.json

# Edit site config directly
docker compose -f production/production.yaml exec backend bash

# Inside container:
cd sites/erp.example.com
nano site_config.json

# Add these lines:
{
  "app_include_css": ["/assets/custom.css"],
  "app_logo_url": "/assets/favicon.png",
  # ... other config ...
}

# Exit and rebuild
exit

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

Step 6: Verify Branding

# Clear cache
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

# Rebuild assets
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

# Restart services
docker compose -f production/production.yaml restart backend frontend

In Browser:

  1. Open https://erp.example.com
  2. Hard refresh: Ctrl + Shift + R (or Cmd + Shift + R on Mac)
  3. Check browser tab for your favicon
  4. Verify colors match your brand
  5. Check that "Powered by ERPNext" is hidden

Updating Branding

When you need to update your branding:

# 1. Edit customization files
nano production/customizations/custom.css
# or replace files:
cp ~/new-logo.png production/customizations/logo.png

# 2. Restart services (mounts are read-only, no rebuild needed)
docker compose -f production/production.yaml restart backend frontend

# 3. Clear cache and rebuild
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

# 4. Hard refresh browser (Ctrl+Shift+R)

What Gets Customized

Via File Mounts + CSS:

  • Favicon (browser tab icon)
  • Brand colors (navbar, buttons, links)
  • Login page styling
  • Hide "Powered by ERPNext" footer
  • Custom CSS overrides

Via ERPNext Settings:

  • Website logo (Setup → Website Settings)
  • App name (Setup → System Settings)
  • Website theme colors
  • Custom HTML/CSS includes

Troubleshooting Branding

Branding not showing:

# 1. Check files exist
ls -la production/customizations/

# 2. Check files are mounted
docker compose -f production/production.yaml exec backend \
  ls -la /home/frappe/frappe-bench/sites/assets/

# 3. Check site config
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com show-config | grep -i css

# 4. Force rebuild
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

# 5. Restart services
docker compose -f production/production.yaml restart backend frontend

CSS not applied:

# Verify CSS is loaded (check browser console)
# Should see: /assets/custom.css

# If not, set in site_config.json:
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com set-config app_include_css '["/assets/custom.css"]'

Favicon not showing:

# Set favicon URL
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com set-config app_logo_url '/assets/favicon.png'

# Or via Website Settings in UI

Advanced: Custom Frappe App for Branding

For complex branding needs, create a custom Frappe app:

# Inside backend container
docker compose -f production/production.yaml exec backend bash

# Create custom app
cd /home/frappe/frappe-bench
bench new-app custom_theme

# Add your customizations to:
# apps/custom_theme/custom_theme/public/css/
# apps/custom_theme/custom_theme/public/js/

# Install app to site
bench --site erp.example.com install-app custom_theme

# Exit container
exit

This approach is recommended for:

  • Complex UI changes
  • Multiple sites with different branding
  • Custom JavaScript functionality
  • Version-controlled branding

Common Operations

Backup Site

# Basic backup
./scripts/backup-site.sh erp.example.com

# Advanced backup with files and auto-copy
./scripts/backup-site.sh erp.example.com --with-files --auto-copy

# Encrypted backup with cleanup
BACKUP_PASSPHRASE='your-secret' ./scripts/backup-site.sh erp.example.com \
  --encrypt --auto-copy --cleanup-old

Backup Features:

  • Database + Files: Use --with-files to include uploaded files
  • Auto-copy: --auto-copy copies backups to host ./backups/ directory
  • Encryption: --encrypt with GPG AES256 (requires BACKUP_PASSPHRASE)
  • Cleanup: --cleanup-old removes backups older than BACKUP_RETENTION_DAYS
  • Validation: Automatic backup verification and size reporting

Backups are stored in: sites/erp.example.com/private/backups/ (container) or ./backups/ (host)

View Logs

# Interactive service selection
./scripts/logs.sh

# Specific services
./scripts/logs.sh backend     # Application logs
./scripts/logs.sh frontend    # Nginx logs
./scripts/logs.sh scheduler   # Background jobs
./scripts/logs.sh all         # All services

# Alternative: direct Docker commands
docker compose -f production/production.yaml logs -f backend

Follow logs (live)

./production/scripts/logs.sh -f backend


### Stop Services

```bash
# Interactive mode (asks about dependencies)
./scripts/stop.sh

# Stop everything without prompts
./scripts/stop.sh --all

Restart Services

./scripts/stop.sh --all
./scripts/deploy.sh

Update ERPNext

# Update version in production.env
nano production/production.env
# Change: ERPNEXT_VERSION=v15.83.0

# Pull new images
docker compose -f production/production.yaml pull

# Redeploy
./production/scripts/deploy.sh

# Run migrations
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com migrate

Add New Site (Multi-tenancy)

# Update SITES in production.env
nano production/production.env
# Change: SITES=erp.example.com,erp2.example.com

# Regenerate configuration
./scripts/deploy.sh --regenerate

# Apply changes
docker compose -f production/production.yaml up -d

# Create new site
./scripts/create-site.sh erp2.example.com

# Apply branding to new site (manual steps)
# 1. Set CSS include
docker compose -f production/production.yaml exec backend \
  bench --site erp2.example.com set-config app_include_css '["/assets/custom.css"]'

# 2. Set logo/favicon
docker compose -f production/production.yaml exec backend \
  bench --site erp2.example.com set-config app_logo_url '/assets/favicon.png'

# 3. Build and clear cache
docker compose -f production/production.yaml exec backend \
  bench --site erp2.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp2.example.com build --force

Update Procedures

Understanding the Update Layers

This deployment has three layers that update independently:

  1. Infrastructure Layer (frappe_docker)

    • Docker Compose configurations
    • Container build instructions
    • Deployment scripts structure
    • Updates via: git fetch upstream && git merge upstream/main
  2. Application Layer (ERPNext/Frappe)

    • ERPNext features and bug fixes
    • Frappe framework updates
    • Updates via: Changing version tags in production.env
  3. Customization Layer (Your Branding)

    • Custom CSS, logos, favicon
    • Your deployment scripts
    • Updates via: Editing files in production/customizations/

Update Infrastructure (Docker Configs)

What this updates: Compose files, build configs, deployment improvements

# 1. Sync with upstream frappe_docker
cd /path/to/erp-is-test
git fetch upstream
git merge upstream/main

# 2. Review infrastructure changes
git log --oneline upstream/main ^HEAD
git diff HEAD~1 compose.yaml
git diff HEAD~1 overrides/

# 3. Regenerate production.yaml with new infrastructure
./scripts/deploy.sh --regenerate

# 4. Review generated configuration
docker compose -f production/production.yaml config > /tmp/new-config.yaml
diff production/production.yaml /tmp/new-config.yaml || true

# 5. Apply infrastructure updates
docker compose -f production/production.yaml up -d

# 6. Verify all services healthy
docker compose -f production/production.yaml ps

Important: This does NOT update ERPNext version, only how it runs.

Update ERPNext Version

What this updates: ERPNext features, bug fixes, Frappe framework

# 1. Check current version
docker compose -f production/production.yaml exec backend bench version

# 2. Backup before upgrading
./scripts/backup-site.sh erp.example.com --with-files --auto-copy

# 3. Update version in production.env
nano production/production.env
# Change:
# ERPNEXT_VERSION=v15.82.1  →  ERPNEXT_VERSION=v15.85.0
# FRAPPE_VERSION=v15.82.1   →  FRAPPE_VERSION=v15.85.0

# 4. Regenerate configuration
./scripts/deploy.sh --regenerate

# 5. Pull new images (downloads updated ERPNext)
docker compose -f production/production.yaml pull

# 6. Stop services
./scripts/stop.sh

# 7. Start with new version
docker compose -f production/production.yaml up -d

# 8. Run database migrations
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com migrate

# 9. Clear cache and rebuild
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build

# 10. Verify new version
docker compose -f production/production.yaml exec backend bench version

Update Branding

What this updates: Your custom CSS, logos, favicon

# 1. Edit customization files
nano production/customizations/custom.css
# or replace:
cp ~/new-logo.png production/customizations/logo.png

# 2. Restart services to mount new files
docker compose -f production/production.yaml restart backend frontend

# 3. Clear cache and rebuild
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

# 4. Clear browser cache (Ctrl+Shift+R)

Complete Update (All Layers)

Use this for major version jumps or after long periods:

# 1. Backup everything first
./scripts/backup-site.sh erp.example.com --with-files --auto-copy

# 2. Update infrastructure
git fetch upstream && git merge upstream/main

# 3. Update ERPNext version in production.env
nano production/production.env

# 4. Regenerate everything
./scripts/deploy.sh --regenerate

# 5. Deploy
./scripts/stop.sh
docker compose -f production/production.yaml pull
docker compose -f production/production.yaml up -d

# 6. Migrate
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com migrate

# 7. Rebuild branding and cache
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

Scheduled Maintenance Window

Recommended schedule:

# Monthly: Infrastructure updates
# - Review frappe_docker upstream changes
# - Merge if stability improvements available
# - Test in staging first

# Quarterly: ERPNext version updates
# - Check release notes: https://erpnext.com/version-15
# - Backup before upgrading
# - Update during low-traffic period

# As needed: Branding updates
# - No downtime required
# - Can be done anytime

Rollback Procedures

If update fails:

# 1. Restore from backup
# (Backup is in sites/SITENAME/private/backups/ or ./backups/)

# 2. Revert version in production.env
nano production/production.env
# Change back to previous version

# 3. Regenerate and redeploy
./scripts/deploy.sh --regenerate
./scripts/stop.sh
docker compose -f production/production.yaml up -d

# 4. Restore database backup
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com restore /path/to/backup/database.sql.gz

Troubleshooting

Site Not Accessible

Check DNS:

dig +short erp.example.com
nslookup erp.example.com

Check Traefik:

docker compose -f production/production.yaml logs traefik

Check frontend:

docker compose -f production/production.yaml logs frontend

SSL Certificate Issues

Check Let's Encrypt logs:

docker compose -f production/production.yaml logs traefik | grep -i acme

Common causes:

  • DNS not propagated (wait 24-48 hours)
  • Ports 80/443 not open
  • Rate limit (5 certs/week per domain)

Test certificate:

curl -vI https://erp.example.com 2>&1 | grep -i certificate

Database Connection Failed

Check MariaDB:

docker compose -f production/production.yaml logs mariadb

Test connection:

docker compose -f production/production.yaml exec backend \
  mysql -h mariadb-database -u root -p
# Enter DB_PASSWORD from production.env

Common causes:

  • DB_PASSWORD mismatch between production.env and mariadb.env
  • DB_HOST should be mariadb-database (container name)

Branding Not Applied

Check files exist:

ls -la production/customizations/
# Should show: custom.css, favicon.ico, favicon.png, logo.png

Check mounts:

docker compose -f production/production.yaml exec backend \
  ls -la /home/frappe/frappe-bench/sites/assets/

Reapply branding manually:

# 1. Verify volume mounts in production.yaml
grep -A5 "customizations" production/production.yaml

# 2. Restart services
docker compose -f production/production.yaml restart backend frontend

# 3. Set CSS and favicon in site config
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com set-config app_include_css '["/assets/custom.css"]'

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com set-config app_logo_url '/assets/favicon.png'

# 4. Clear cache and rebuild
docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com clear-cache

docker compose -f production/production.yaml exec backend \
  bench --site erp.example.com build --force

Service Crash Loops

Check logs:

./scripts/logs.sh <service-name>

Check resources:

docker stats
free -h
df -h

Restart:

./scripts/stop.sh --all
./scripts/deploy.sh

Performance Issues

Check worker count:

docker ps | grep erpnext-production

Scale workers (edit production/production.env):

WORKERS=4
QUEUE_WORKERS=6

Then redeploy:

./production/scripts/deploy.sh --regenerate  # Regenerate config
./production/scripts/deploy.sh               # Apply changes

Security

1. Never Commit Secrets

# Verify .env files are ignored
git check-ignore production/production.env
git check-ignore production/mariadb.env
git check-ignore production/traefik.env

# All should return the filename (means they're ignored)

2. Use Strong Passwords

# Always generate with OpenSSL
openssl rand -base64 32  # 32 characters
openssl rand -base64 48  # 48 characters (more secure)

3. Restrict File Permissions

chmod 600 production/*.env
chmod 755 production/scripts/*.sh

4. Enable Firewall

sudo ufw enable
sudo ufw status

5. Keep System Updated

# System updates
sudo apt update && sudo apt upgrade -y

# Docker updates
sudo apt install --only-upgrade docker-ce docker-compose-plugin

Setup Automated Backups

# Add to crontab
crontab -e

# Daily backup at 2 AM with auto-copy and cleanup
0 2 * * * cd /path/to/erp-is-test/production && AUTO_COPY=1 CLEANUP_OLD=1 ./scripts/backup-site.sh erp.example.com

# Weekly encrypted backup
0 3 * * 0 cd /path/to/erp-is-test/production && BACKUP_PASSPHRASE='your-secret' ./scripts/backup-site.sh erp.example.com --encrypt --auto-copy

Backup Encryption

Setup GPG for backups:

# Install GPG (usually pre-installed)
sudo apt install gnupg -y

# Set backup passphrase
export BACKUP_PASSPHRASE='your-very-secure-passphrase'

# Create encrypted backup
./scripts/backup-site.sh erp.example.com --encrypt --auto-copy

Decrypt backups:

# Decrypt single file
gpg --decrypt backup-file.gpg > backup-file

# Decrypt with passphrase from environment
echo "$BACKUP_PASSPHRASE" | gpg --batch --passphrase-fd 0 --decrypt file.gpg > file

# Decrypt all .gpg files in directory
for f in *.gpg; do gpg --decrypt "$f" > "${f%.gpg}"; done

Backup Monitoring

Check backup status:

# View backup logs
tail -f /tmp/erpnext-backup-$(date +%Y%m%d).log

# List backups
ls -lah ./backups/

# Verify backup integrity
./scripts/backup-site.sh erp.example.com --debug

7. Monitor Logs

# Setup log rotation
sudo nano /etc/logrotate.d/docker-compose

# Add:
/path/to/erp-is/production/logs/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
}

8. Backup Your Passwords

Store securely in password manager:

  • Server IP and SSH credentials
  • Domain and DNS credentials
  • Database passwords (DB_PASSWORD)
  • Admin password (ADMIN_PASSWORD)
  • Traefik dashboard password
  • Backup encryption passphrase
  • SSL certificate information

Environment Variables Reference

Global Script Variables:

# Project configuration
PROJECT_NAME=erpnext-production           # Docker project name
BACKUP_RETENTION_DAYS=30                  # Backup retention period

# Backup script variables
AUTO_COPY=1                               # Auto-copy to host
CLEANUP_OLD=1                             # Auto-cleanup old backups
BACKUP_PASSPHRASE='your-secret'           # GPG encryption passphrase
DEBUG=1                                   # Enable debug output

# Usage example
AUTO_COPY=1 CLEANUP_OLD=1 DEBUG=1 ./scripts/backup-site.sh erp.example.com

Backup Environment Variables:

# Set persistent environment variables
echo 'export BACKUP_PASSPHRASE="your-secure-passphrase"' >> ~/.bashrc
echo 'export AUTO_COPY=1' >> ~/.bashrc
echo 'export CLEANUP_OLD=1' >> ~/.bashrc
source ~/.bashrc

# Now backups are simpler
./scripts/backup-site.sh erp.example.com --encrypt

Environment Files Explained

Why Three Files?

Different Docker Compose projects use different files:

  1. Traefik projecttraefik.env

    • Reverse proxy configuration
    • SSL certificate management
    • Dashboard access
  2. MariaDB projectmariadb.env

    • Database root password
    • Shared across all ERPNext instances
  3. ERPNext projectproduction.env

    • Application configuration
    • Database connection
    • Redis configuration
    • Admin password

Critical: DB Password Must Match

# production.env
DB_PASSWORD=Q2f7k9Lm3nP5rT8wX1zC4vB6hJ0yN2sA

# mariadb.env
DB_PASSWORD=Q2f7k9Lm3nP5rT8wX1zC4vB6hJ0yN2sA  # ← MUST BE SAME!

If different, ERPNext cannot connect to database.


Architecture Explained

Image Sources

This setup uses official Frappe Docker images:

# Your fork provides:
- Infrastructure: compose.yaml, deployment scripts
- Customizations: production/customizations/, branding

# Frappe provides:
- Container images: frappe/erpnext, frappe/frappe
- Base configurations: upstream compose files

Image pull locations:

# From Docker Hub (official Frappe images)
frappe/erpnext:v15.82.1
frappe/frappe:v15.82.1
library/mariadb:11.8
library/redis:alpine
traefik:v2.11

You maintain:

  • production/ directory (deployment scripts, configs)
  • production/customizations/ (branding files)
  • .gitignore (excludes *.env files)

You track upstream:

  • compose.yaml (base infrastructure)
  • overrides/compose.*.yaml (feature overlays)
  • images/*/Containerfile (if you need custom builds)

Why This Approach?

Benefits:

  • Get official, tested ERPNext images
  • Receive infrastructure updates from frappe_docker
  • Keep your customizations separate
  • Easy to merge upstream improvements
  • No need to rebuild images for simple branding

When you'd build custom images:

  • Need to modify Python dependencies
  • Add system packages to containers
  • Install custom Frappe apps (beyond branding)

Script Optimizations

All automation scripts have been significantly optimized for better performance, maintainability, and user experience.

📊 Optimization Results

Script Before After Reduction Features Added
backup-site.sh 469 lines 224 lines 52% Help, encryption, validation
create-site.sh 118 lines 96 lines 19% Help, better prompts
deploy.sh 287 lines 156 lines 46% Help, cleaner output
logs.sh 106 lines 65 lines 39% Help, menu interface
stop.sh 85 lines 68 lines 20% Help, --all option
validate-env.sh 238 lines 241 lines +1% Help (already optimal)
TOTAL 1,303 850 35% 100% help coverage

🚀 Key Improvements

1. Comprehensive Help System

# Every script now has detailed help
./scripts/deploy.sh --help
./scripts/backup-site.sh -h
./scripts/logs.sh --help

2. Condensed Code Structure

  • One-liner helper functions
  • Compact conditional statements
  • DRY (Don't Repeat Yourself) principles
  • Smart use of bash shortcuts

3. Enhanced Backup Features

  • GPG Encryption: AES256 symmetric encryption
  • Auto-copy: Container → host backup transfer
  • Cleanup: Automated old backup removal
  • Validation: File verification and size reporting
  • Logging: Detailed operation logs

4. Interactive Log Viewer

  • Menu-driven service selection
  • Support for both numbers (1-7) and names
  • Real-time log following
  • Clear service descriptions

5. Smart Deployment

  • Built-in validation before deployment
  • Cleaner progress output
  • Helper functions for repetitive tasks
  • Better error handling

6. Improved Stop Script

  • Selective service stopping
  • --all flag for automation
  • Interactive confirmation for dependencies

📝 Technical Details

Before (verbose):

echo_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

if [ -z "$SITE_NAME" ]; then
    echo_error "Site name cannot be empty"
    exit 1
fi

After (lean):

echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; }

[[ -z "$SITE_NAME" ]] && { echo_error "Site name cannot be empty"; exit 1; }

Result: 60% fewer lines with identical functionality.

🛡️ Maintained Compatibility

All original functionality preserved
Same command-line interfaces
No breaking changes
Enhanced error handling
Better user experience

For detailed optimization information, see: scripts/OPTIMIZATION_SUMMARY.md


Support


Tested With: ERPNext v15.82.1, Docker 24.0+, Ubuntu 22.04 LTS
Script Optimization: 35% reduction in code, 100% help coverage
Last Updated: October 2025