- Created example environment files for MariaDB and production settings. - Implemented backup script for ERPNext sites with options for file inclusion, compression, and encryption. - Developed site creation script to streamline ERPNext site setup with admin password handling. - Added deployment script to manage the deployment of ERPNext, MariaDB, and Traefik services. - Introduced log viewing script for monitoring ERPNext services. - Implemented stop script to manage stopping of ERPNext and its dependencies. - Added validation script to check environment configuration for common issues and security best practices.
41 KiB
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