| .. | ||
| scripts | ||
| mariadb.env.example | ||
| production.env.example | ||
| README.md | ||
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:
- Traefik - Reverse proxy, SSL certificates, load balancing
- MariaDB - Shared database for all ERPNext sites
- 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
- Prerequisites
- Pre-Deployment Checklist
- Environment Configuration
- Deployment
- Script Usage Guide
- Branding Customization
- Common Operations
- Update Procedures
- Troubleshooting
- Security
- Architecture Explained
- Script Optimizations
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:
- backend - Gunicorn application server
- frontend - Nginx reverse proxy
- websocket - Socket.io for real-time features
- queue-short - Short-running background jobs
- queue-long - Long-running background jobs
- scheduler - Cron-like background scheduler
- 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
production/production.env- ERPNext application configproduction/mariadb.env- Database configproduction/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:
- Validates environment configuration (
./scripts/validate-env.sh) - Deploys Traefik (reverse proxy + SSL)
- Deploys MariaDB (database)
- 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
- Base:
- 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,#3182cewith 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)
-
Login to ERPNext:
https://erp.example.com -
Go to: Setup → Website Settings
-
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
- Favicon: Upload or set path to
-
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:
- Open
https://erp.example.com - Hard refresh:
Ctrl + Shift + R(orCmd + Shift + Ron Mac) - Check browser tab for your favicon
- Verify colors match your brand
- 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-filesto include uploaded files - Auto-copy:
--auto-copycopies backups to host./backups/directory - Encryption:
--encryptwith GPG AES256 (requiresBACKUP_PASSPHRASE) - Cleanup:
--cleanup-oldremoves backups older thanBACKUP_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:
-
Infrastructure Layer (frappe_docker)
- Docker Compose configurations
- Container build instructions
- Deployment scripts structure
- Updates via:
git fetch upstream && git merge upstream/main
-
Application Layer (ERPNext/Frappe)
- ERPNext features and bug fixes
- Frappe framework updates
- Updates via: Changing version tags in
production.env
-
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_PASSWORDmismatch betweenproduction.envandmariadb.envDB_HOSTshould bemariadb-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:
-
Traefik project →
traefik.env- Reverse proxy configuration
- SSL certificate management
- Dashboard access
-
MariaDB project →
mariadb.env- Database root password
- Shared across all ERPNext instances
-
ERPNext project →
production.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
--allflag 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
- ERPNext Forum: https://discuss.erpnext.com/
- Documentation: https://docs.erpnext.com/
- Upstream Repository: https://github.com/frappe/frappe_docker
- GitHub Issues: https://github.com/frappe/frappe_docker/issues
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