Last active
February 27, 2025 03:12
-
-
Save rhcarlosweb/0bda9a02aaf0746d94a38cbb07a0adc3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# n8n Docker Installation Script with Traefik, Redis, PostgreSQL, and Queue Mode | |
# This script sets up n8n with Docker Compose including: | |
# - Traefik as reverse proxy with Let's Encrypt SSL | |
# - Redis for caching | |
# - PostgreSQL for database | |
# - n8n in queue mode with 2 workers | |
# Force execution with bash | |
if [ -z "$BASH_VERSION" ]; then | |
echo "This script must be run with bash. Trying to switch to bash automatically..." | |
exec /bin/bash "$0" "$@" | |
# If exec fails, the script will continue but might fail later | |
echo "Failed to switch to bash. Please run the script with bash explicitly: bash $0" | |
fi | |
set -e | |
# Text formatting | |
BOLD="\033[1m" | |
GREEN="\033[0;32m" | |
YELLOW="\033[0;33m" | |
RED="\033[0;31m" | |
NC="\033[0m" # No Color | |
# Function to print colored messages | |
print_message() { | |
echo -e "${2}${1}${NC}" | |
} | |
# Function to check if command exists | |
command_exists() { | |
command -v "$1" >/dev/null 2>&1 | |
} | |
# Check if running as root | |
if [ "$(id -u)" -ne 0 ]; then | |
print_message "This script must be run as root" "$RED" | |
exit 1 | |
fi | |
# Check system requirements | |
print_message "Checking system requirements..." "$BOLD" | |
# Create installation directory | |
INSTALL_DIR="/opt/n8n" | |
mkdir -p "$INSTALL_DIR" | |
cd "$INSTALL_DIR" | |
# Install dependencies if not already installed | |
print_message "Installing dependencies..." "$BOLD" | |
apt-get update | |
apt-get install -y \ | |
apt-transport-https \ | |
ca-certificates \ | |
curl \ | |
gnupg \ | |
lsb-release \ | |
apache2-utils \ | |
pwgen | |
# Install Docker if not already installed | |
if ! command_exists docker; then | |
print_message "Installing Docker..." "$BOLD" | |
curl -fsSL https://get.docker.com -o get-docker.sh | |
sh get-docker.sh | |
rm get-docker.sh | |
else | |
print_message "Docker already installed. Skipping..." "$GREEN" | |
fi | |
# Install Docker Compose if not already installed | |
if ! command_exists docker-compose; then | |
print_message "Installing Docker Compose..." "$BOLD" | |
LATEST_COMPOSE=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep -o -P '(?<="tag_name": ").+(?=")') | |
curl -L "https://github.com/docker/compose/releases/download/${LATEST_COMPOSE}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose | |
chmod +x /usr/local/bin/docker-compose | |
else | |
print_message "Docker Compose already installed. Skipping..." "$GREEN" | |
fi | |
# Interactive configuration | |
print_message "\nStarting interactive configuration...\n" "$BOLD" | |
# Domain configuration | |
while true; do | |
read -p "Enter domain name (e.g., example.com): " DOMAIN_NAME | |
if [ ! -z "$DOMAIN_NAME" ]; then | |
break | |
else | |
print_message "Domain name cannot be empty!" "$RED" | |
fi | |
done | |
# Subdomain configuration | |
read -p "Enter subdomain for n8n [n8n]: " SUBDOMAIN | |
SUBDOMAIN=${SUBDOMAIN:-n8n} | |
# Full domain | |
FULL_DOMAIN="${SUBDOMAIN}.${DOMAIN_NAME}" | |
# Frontend URL configuration (Vite) | |
read -p "Enter frontend URL for n8n [https://${FULL_DOMAIN}]: " FRONTEND_URL | |
FRONTEND_URL=${FRONTEND_URL:-https://${FULL_DOMAIN}} | |
# Email for Let's Encrypt | |
while true; do | |
read -p "Enter email address for Let's Encrypt notifications: " EMAIL | |
if [ ! -z "$EMAIL" ]; then | |
break | |
else | |
print_message "Email address cannot be empty!" "$RED" | |
fi | |
done | |
# Timezone configuration | |
read -p "Enter timezone [Europe/Berlin]: " TIMEZONE | |
TIMEZONE=${TIMEZONE:-Europe/Berlin} | |
# Local file access | |
read -p "Do you want to set up local file access for n8n? (y/n) [n]: " SETUP_LOCAL_FILES | |
SETUP_LOCAL_FILES=${SETUP_LOCAL_FILES:-n} | |
if [[ "$SETUP_LOCAL_FILES" == "y" || "$SETUP_LOCAL_FILES" == "Y" ]]; then | |
read -p "Enter local path for file access [/local-files]: " LOCAL_FILES_PATH | |
LOCAL_FILES_PATH=${LOCAL_FILES_PATH:-/local-files} | |
mkdir -p $LOCAL_FILES_PATH | |
chmod 777 $LOCAL_FILES_PATH | |
fi | |
# PostgreSQL configuration | |
read -p "Enter PostgreSQL username [n8nuser]: " PG_USER | |
PG_USER=${PG_USER:-n8nuser} | |
# Generate random PostgreSQL password if not provided | |
PG_PASS=$(pwgen -s 16 1) | |
read -p "Enter PostgreSQL password [generated: $PG_PASS]: " USER_PG_PASS | |
PG_PASS=${USER_PG_PASS:-$PG_PASS} | |
read -p "Enter PostgreSQL database name [n8n]: " PG_DB | |
PG_DB=${PG_DB:-n8n} | |
# n8n configuration | |
read -p "Enter n8n username for initial login: " N8N_USER | |
# Generate random n8n password if not provided | |
N8N_PASS=$(pwgen -s 16 1) | |
read -p "Enter n8n password [generated: $N8N_PASS]: " USER_N8N_PASS | |
N8N_PASS=${USER_N8N_PASS:-$N8N_PASS} | |
# Generate encryption key for n8n | |
ENCRYPTION_KEY=$(pwgen -s 32 1) | |
# Number of workers | |
read -p "Enter number of n8n workers [2]: " N8N_WORKERS | |
N8N_WORKERS=${N8N_WORKERS:-2} | |
# Create Docker volumes | |
print_message "Creating Docker volumes..." "$BOLD" | |
docker volume create traefik_data | |
docker volume create n8n_data | |
docker volume create postgres_data | |
docker volume create redis_data | |
# Create docker-compose.yml | |
print_message "Creating Docker Compose configuration..." "$BOLD" | |
cat > docker-compose.yml << EOL | |
version: '3.8' | |
networks: | |
traefik-public: | |
external: false | |
internal: | |
external: false | |
services: | |
traefik: | |
image: traefik:v2.10 | |
container_name: traefik | |
restart: always | |
command: | |
- "--api=true" | |
- "--api.insecure=false" | |
- "--providers.docker=true" | |
- "--providers.docker.exposedbydefault=false" | |
- "--entrypoints.web.address=:80" | |
- "--entrypoints.web.http.redirections.entryPoint.to=websecure" | |
- "--entrypoints.web.http.redirections.entrypoint.scheme=https" | |
- "--entrypoints.websecure.address=:443" | |
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" | |
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}" | |
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" | |
ports: | |
- "80:80" | |
- "443:443" | |
volumes: | |
- traefik_data:/letsencrypt | |
- /var/run/docker.sock:/var/run/docker.sock:ro | |
networks: | |
- traefik-public | |
postgres: | |
image: postgres:14-alpine | |
container_name: postgres | |
restart: always | |
environment: | |
- POSTGRES_USER=${PG_USER} | |
- POSTGRES_PASSWORD=${PG_PASS} | |
- POSTGRES_DB=${PG_DB} | |
- POSTGRES_NON_ROOT_USER=${PG_USER} | |
volumes: | |
- postgres_data:/var/lib/postgresql/data | |
networks: | |
- internal | |
labels: | |
- "traefik.enable=false" | |
redis: | |
image: redis:7-alpine | |
container_name: redis | |
restart: always | |
command: redis-server --requirepass ${PG_PASS} | |
volumes: | |
- redis_data:/data | |
networks: | |
- internal | |
labels: | |
- "traefik.enable=false" | |
n8n: | |
image: docker.n8n.io/n8nio/n8n:latest | |
container_name: n8n | |
restart: always | |
environment: | |
- N8N_HOST=${FULL_DOMAIN} | |
- N8N_PROTOCOL=https | |
- N8N_PORT=5678 | |
- N8N_EDITOR_BASE_URL=${FRONTEND_URL} | |
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY} | |
- NODE_ENV=production | |
- WEBHOOK_URL=https://${FULL_DOMAIN}/ | |
- DB_TYPE=postgresdb | |
- DB_POSTGRESDB_HOST=postgres | |
- DB_POSTGRESDB_PORT=5432 | |
- DB_POSTGRESDB_DATABASE=${PG_DB} | |
- DB_POSTGRESDB_USER=${PG_USER} | |
- DB_POSTGRESDB_PASSWORD=${PG_PASS} | |
- QUEUE_BULL_REDIS_HOST=redis | |
- QUEUE_BULL_REDIS_PORT=6379 | |
- QUEUE_BULL_REDIS_PASSWORD=${PG_PASS} | |
- EXECUTIONS_MODE=queue | |
- QUEUE_BULL_REDIS_DB=0 | |
- N8N_METRICS=true | |
- N8N_USER_MANAGEMENT_DISABLED=false | |
- N8N_DIAGNOSTICS_ENABLED=false | |
- N8N_DEFAULT_USER_NAME=${N8N_USER} | |
- N8N_DEFAULT_USER_PASSWORD=${N8N_PASS} | |
- N8N_HIRING_BANNER_ENABLED=false | |
- GENERIC_TIMEZONE=${TIMEZONE} | |
volumes: | |
- n8n_data:/home/node/.n8n | |
EOL | |
# Add local file access if requested | |
if [[ "$SETUP_LOCAL_FILES" == "y" || "$SETUP_LOCAL_FILES" == "Y" ]]; then | |
cat >> docker-compose.yml << EOL | |
- ${LOCAL_FILES_PATH}:/files | |
EOL | |
fi | |
# Continue the n8n configuration | |
cat >> docker-compose.yml << EOL | |
networks: | |
- internal | |
- traefik-public | |
labels: | |
- "traefik.enable=true" | |
- "traefik.http.routers.n8n.rule=Host(\`${FULL_DOMAIN}\`)" | |
- "traefik.http.routers.n8n.entrypoints=websecure" | |
- "traefik.http.routers.n8n.tls=true" | |
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt" | |
- "traefik.http.services.n8n.loadbalancer.server.port=5678" | |
- "traefik.http.middlewares.n8n-headers.headers.SSLRedirect=true" | |
- "traefik.http.middlewares.n8n-headers.headers.STSSeconds=315360000" | |
- "traefik.http.middlewares.n8n-headers.headers.browserXSSFilter=true" | |
- "traefik.http.middlewares.n8n-headers.headers.contentTypeNosniff=true" | |
- "traefik.http.middlewares.n8n-headers.headers.forceSTSHeader=true" | |
- "traefik.http.middlewares.n8n-headers.headers.SSLHost=${DOMAIN_NAME}" | |
- "traefik.http.middlewares.n8n-headers.headers.STSIncludeSubdomains=true" | |
- "traefik.http.middlewares.n8n-headers.headers.STSPreload=true" | |
- "traefik.http.routers.n8n.middlewares=n8n-headers" | |
depends_on: | |
- postgres | |
- redis | |
EOL | |
# Add worker nodes to docker-compose.yml | |
for i in $(seq 1 $N8N_WORKERS); do | |
cat >> docker-compose.yml << EOL | |
n8n-worker-${i}: | |
image: docker.n8n.io/n8nio/n8n:latest | |
container_name: n8n-worker-${i} | |
restart: always | |
environment: | |
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY} | |
- N8N_EDITOR_BASE_URL=${FRONTEND_URL} | |
- NODE_ENV=production | |
- DB_TYPE=postgresdb | |
- DB_POSTGRESDB_HOST=postgres | |
- DB_POSTGRESDB_PORT=5432 | |
- DB_POSTGRESDB_DATABASE=${PG_DB} | |
- DB_POSTGRESDB_USER=${PG_USER} | |
- DB_POSTGRESDB_PASSWORD=${PG_PASS} | |
- QUEUE_BULL_REDIS_HOST=redis | |
- QUEUE_BULL_REDIS_PORT=6379 | |
- QUEUE_BULL_REDIS_PASSWORD=${PG_PASS} | |
- EXECUTIONS_MODE=queue | |
- QUEUE_BULL_REDIS_DB=0 | |
- N8N_METRICS=false | |
- WORKERS_DISABLED=false | |
- WEBHOOK_TUNNEL_URL=https://${FULL_DOMAIN}/ | |
- GENERIC_TIMEZONE=${TIMEZONE} | |
command: worker | |
volumes: | |
- n8n_data:/home/node/.n8n | |
EOL | |
# Add local file access if requested | |
if [[ "$SETUP_LOCAL_FILES" == "y" || "$SETUP_LOCAL_FILES" == "Y" ]]; then | |
cat >> docker-compose.yml << EOL | |
- ${LOCAL_FILES_PATH}:/files | |
EOL | |
fi | |
# Continue the worker configuration | |
cat >> docker-compose.yml << EOL | |
networks: | |
- internal | |
labels: | |
- "traefik.enable=false" | |
depends_on: | |
- postgres | |
- redis | |
- n8n | |
EOL | |
done | |
# Add volumes to docker-compose.yml | |
cat >> docker-compose.yml << EOL | |
volumes: | |
traefik_data: | |
external: true | |
n8n_data: | |
external: true | |
postgres_data: | |
external: true | |
redis_data: | |
external: true | |
EOL | |
# Create a script to start the services | |
cat > start.sh << EOL | |
#!/bin/bash | |
docker-compose up -d | |
echo "n8n has been started!" | |
EOL | |
chmod +x start.sh | |
# Create a script to stop the services | |
cat > stop.sh << EOL | |
#!/bin/bash | |
docker-compose down | |
echo "n8n has been stopped!" | |
EOL | |
chmod +x stop.sh | |
# Create a script to restart the services | |
cat > restart.sh << EOL | |
#!/bin/bash | |
docker-compose down | |
docker-compose up -d | |
echo "n8n has been restarted!" | |
EOL | |
chmod +x restart.sh | |
# Create a script to check the logs | |
cat > logs.sh << EOL | |
#!/bin/bash | |
docker-compose logs -f \$1 | |
EOL | |
chmod +x logs.sh | |
# Create a backup script | |
cat > backup.sh << EOL | |
#!/bin/bash | |
# Create timestamp for the backup | |
TIMESTAMP=\$(date +%Y%m%d_%H%M%S) | |
BACKUP_DIR="/opt/n8n/backups/\$TIMESTAMP" | |
# Create backup directory | |
mkdir -p \$BACKUP_DIR | |
# Stop n8n if running | |
docker-compose down | |
# Backup PostgreSQL database | |
docker run --rm -v postgres_data:/var/lib/postgresql/data -v \$BACKUP_DIR:/backup postgres:14-alpine sh -c "pg_dump -U ${PG_USER} -d ${PG_DB} -f /backup/n8n_db.sql" | |
# Backup n8n data | |
docker run --rm -v n8n_data:/source -v \$BACKUP_DIR:/backup alpine sh -c "cp -r /source /backup/n8n_data" | |
# Restart n8n | |
docker-compose up -d | |
echo "Backup completed at \$BACKUP_DIR" | |
EOL | |
chmod +x backup.sh | |
# Create a configuration info file | |
cat > config-info.txt << EOL | |
N8N INSTALLATION INFORMATION | |
=========================== | |
Domain: ${DOMAIN_NAME} | |
Subdomain: ${SUBDOMAIN} | |
Full Domain: ${FULL_DOMAIN} | |
Frontend URL (Vite): ${FRONTEND_URL} | |
Email for Let's Encrypt: ${EMAIL} | |
Timezone: ${TIMEZONE} | |
DATABASE INFORMATION | |
------------------- | |
PostgreSQL User: ${PG_USER} | |
PostgreSQL Password: ${PG_PASS} | |
PostgreSQL Database: ${PG_DB} | |
N8N LOGIN INFORMATION | |
------------------- | |
Username: ${N8N_USER} | |
Password: ${N8N_PASS} | |
MANAGEMENT SCRIPTS | |
---------------- | |
./start.sh - Start all services | |
./stop.sh - Stop all services | |
./restart.sh - Restart all services | |
./logs.sh - View logs (use ./logs.sh n8n for n8n logs) | |
./backup.sh - Create a backup of the database and n8n data | |
N8N URL: ${FRONTEND_URL} | |
EOL | |
# Create .env file for reference | |
cat > .env << EOL | |
# The top level domain | |
DOMAIN_NAME=${DOMAIN_NAME} | |
# The subdomain for n8n | |
SUBDOMAIN=${SUBDOMAIN} | |
# Full domain for n8n | |
FULL_DOMAIN=${FULL_DOMAIN} | |
# Frontend URL for n8n (Vite) | |
FRONTEND_URL=${FRONTEND_URL} | |
# Timezone for n8n | |
GENERIC_TIMEZONE=${TIMEZONE} | |
# Email for SSL certificate | |
SSL_EMAIL=${EMAIL} | |
# Database configuration | |
PG_USER=${PG_USER} | |
PG_PASS=${PG_PASS} | |
PG_DB=${PG_DB} | |
# n8n user credentials | |
N8N_USER=${N8N_USER} | |
N8N_PASS=${N8N_PASS} | |
# n8n encryption key | |
ENCRYPTION_KEY=${ENCRYPTION_KEY} | |
# Number of workers | |
N8N_WORKERS=${N8N_WORKERS} | |
EOL | |
print_message "\nStarting services...\n" "$BOLD" | |
# Create backups directory | |
mkdir -p backups | |
# Start the services | |
./start.sh | |
print_message "\nInstallation completed successfully!" "$GREEN" | |
print_message "Your n8n instance will be available at: ${FRONTEND_URL}" "$GREEN" | |
print_message "Initial login credentials:" "$GREEN" | |
print_message " Username: ${N8N_USER}" "$GREEN" | |
print_message " Password: ${N8N_PASS}" "$GREEN" | |
print_message "\nConfiguration details have been saved to: ${INSTALL_DIR}/config-info.txt" "$YELLOW" | |
print_message "\nIt might take a few minutes for Let's Encrypt to issue the SSL certificate." "$YELLOW" | |
print_message "You can check the status with: ./logs.sh traefik" "$YELLOW" | |
# Final instructions | |
print_message "\nIMPORTANT: Make sure your domain (${FULL_DOMAIN}) is pointing to this server's IP address." "$RED" | |
print_message "Also ensure that ports 80 and 443 are open in your firewall." "$RED" | |
print_message "\nYour n8n instance has authentication enabled. Please keep your credentials safe." "$RED" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment