Created
October 14, 2025 13:33
-
-
Save minaairsupport/cc52c0313d82c3d736cab37ea02a289c 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 | |
# setup_n8n_docker.sh – Provision a self‑hosted n8n instance behind Nginx using Docker, | |
# Docker Compose and an external PostgreSQL database (for example an AWS RDS instance). | |
# | |
# This script installs the required system packages (Docker Engine, Docker Compose / | |
# docker‑compose‑plugin, PostgreSQL client tools, Nginx and Certbot), prepares a | |
# database on the RDS host, writes an `.env` file and a `docker‑compose.yml` | |
# containing services for n8n, a queue worker and Redis, generates (or re‑uses) | |
# an n8n encryption key, configures Nginx as a reverse proxy and obtains a | |
# Let's Encrypt certificate. When it completes successfully you can browse to | |
# https://<your_domain> and log in using the basic‑auth credentials defined in | |
# the `.env` file. This variant is tailored for Amazon Linux 2023 and uses | |
# modern best practices for installing Docker and the Compose plugin. | |
# | |
# Usage: | |
# ./setup_n8n_docker.sh <domain_or_ip> <RDS_HOST> <POSTGRES_USER> <POSTGRES_PASSWORD> <POSTGRES_DB> | |
# | |
# Parameters: | |
# domain_or_ip – The public DNS name or IP address of this EC2 instance. Used | |
# for the Nginx virtual host and TLS certificate. | |
# RDS_HOST – Hostname (endpoint) of your PostgreSQL database instance. | |
# POSTGRES_USER – PostgreSQL username that has permission to create the n8n database. | |
# POSTGRES_PASSWORD – Password for the PostgreSQL user. | |
# POSTGRES_DB – Name of the n8n database to create/use on RDS. | |
# | |
# Example: | |
# ./setup_n8n_docker.sh n8n.example.com mydb.abcdefghijkl.us‑east‑1.rds.amazonaws.com \ | |
# n8nuser strongpassword n8ndb | |
set -euo pipefail | |
# Verify required arguments | |
if [ "$#" -lt 5 ]; then | |
echo "Usage: $0 <domain_or_ip> <RDS_HOST> <POSTGRES_USER> <POSTGRES_PASSWORD> <POSTGRES_DB>" >&2 | |
exit 1 | |
fi | |
DOMAIN_OR_IP="$1" | |
RDS_HOST="$2" | |
POSTGRES_USER="$3" | |
POSTGRES_PASSWORD="$4" | |
POSTGRES_DB="$5" | |
# Unset environment variables that may interfere with psql | |
unset PGUSER | |
unset PGPASSWORD | |
# Check if a command exists | |
command_exists() { | |
command -v "$1" >/dev/null 2>&1 | |
} | |
# Generate a 32‑byte encryption key for n8n | |
generate_encryption_key() { | |
openssl rand -base64 32 | |
} | |
echo "Updating system packages…" | |
sudo yum update -y | |
### Step 1: Install prerequisites | |
echo "Installing PostgreSQL client tools…" | |
if ! command -v psql >/dev/null 2>&1; then | |
# AL2023 uses versioned packages | |
sudo dnf -y install postgresql16 || \ | |
sudo dnf -y install postgresql15 || \ | |
sudo dnf -y install postgresql14 || { | |
echo "ERROR: Could not install PostgreSQL client (psql) on this image."; exit 1; | |
} | |
else | |
echo "PostgreSQL client tools already installed." | |
fi | |
echo "Installing Docker Engine and ensuring Compose is available…" | |
# Install Docker | |
sudo yum install -y docker | |
sudo systemctl enable --now docker | |
sudo usermod -aG docker "$USER" || true | |
# newgrp docker | |
# Ensure Docker Compose v2 is available | |
if docker compose version >/dev/null 2>&1; then | |
echo "Docker Compose v2 is already available." | |
else | |
echo "Installing Docker Compose v2 plugin (manual binary)…" | |
sudo mkdir -p /usr/local/lib/docker/cli-plugins | |
ARCH=$(uname -m) | |
if [ "$ARCH" = "x86_64" ]; then BIN="docker-compose-linux-x86_64" | |
elif [ "$ARCH" = "aarch64" ]; then BIN="docker-compose-linux-aarch64" | |
else echo "Unsupported architecture: $ARCH"; exit 1 | |
fi | |
VER="v2.28.1" | |
sudo curl -SL "https://github.com/docker/compose/releases/download/${VER}/${BIN}" \ | |
-o /usr/local/lib/docker/cli-plugins/docker-compose | |
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose | |
fi | |
echo "Installing Nginx…" | |
sudo yum install -y nginx | |
sudo systemctl enable --now nginx | |
echo "Installing Certbot and its Nginx plugin…" | |
sudo yum install -y certbot python3-certbot-nginx | |
### Step 2: Configure n8n environment | |
WORKDIR="$HOME/n8n" | |
mkdir -p "$WORKDIR" | |
cd "$WORKDIR" | |
echo "Downloading AWS RDS CA bundle…" | |
curl -fsSL https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem -o rds-ca.pem | |
echo "Generating or retrieving the n8n encryption key…" | |
N8N_ENCRYPTION_KEY="" | |
if [ ! -f .env ]; then | |
N8N_ENCRYPTION_KEY=$(generate_encryption_key) | |
else | |
N8N_ENCRYPTION_KEY=$(grep -E '^N8N_ENCRYPTION_KEY=' .env | cut -d '=' -f2 || true) | |
if [ -z "$N8N_ENCRYPTION_KEY" ]; then | |
N8N_ENCRYPTION_KEY=$(generate_encryption_key) | |
fi | |
fi | |
echo "Creating .env file…" | |
cat > .env <<EOF_ENV | |
# AWS RDS configuration | |
POSTGRES_HOST=$RDS_HOST | |
POSTGRES_USER=$POSTGRES_USER | |
POSTGRES_PASSWORD=$POSTGRES_PASSWORD | |
POSTGRES_DB=$POSTGRES_DB | |
# Basic authentication for n8n | |
N8N_BASIC_AUTH_USER=admin | |
N8N_BASIC_AUTH_PASSWORD=changeme | |
# Domain and path configuration | |
DOMAIN_NAME=$DOMAIN_OR_IP | |
N8N_PATH=/ | |
# Redis host | |
REDIS_HOST=redis | |
# n8n encryption key | |
N8N_ENCRYPTION_KEY=$N8N_ENCRYPTION_KEY | |
# Public URLs (behind reverse proxy) | |
N8N_PROTOCOL=https | |
N8N_WEBHOOK_URL=https://${DOMAIN_OR_IP} | |
N8N_EDITOR_BASE_URL=https://${DOMAIN_OR_IP} | |
# Trust your reverse proxy networks (prevents X-Forwarded-For errors) | |
N8N_TRUSTED_PROXIES=loopback,linklocal,uniquelocal,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1,172.17.0.0/16 | |
# Recommended flags | |
N8N_RUNNERS_ENABLED=true | |
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true | |
N8N_GIT_NODE_DISABLE_BARE_REPOS=true | |
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true | |
# Certbot email | |
CERTBOT_EMAIL=admin@$DOMAIN_OR_IP | |
EOF_ENV | |
### Step 3: Initialize the database and create Docker Compose files | |
echo "Creating init-data.sh script…" | |
cat > init-data.sh <<'EOF_INIT' | |
#!/bin/bash | |
RDS_HOST="$1" | |
POSTGRES_USER="$2" | |
POSTGRES_PASSWORD="$3" | |
POSTGRES_DB="$4" | |
export PGPASSWORD="$POSTGRES_PASSWORD" | |
DB_EXISTS=$(psql -v ON_ERROR_STOP=1 --host="$RDS_HOST" --username="$POSTGRES_USER" --dbname="postgres" --tuples-only \ | |
--command="SELECT 1 FROM pg_database WHERE datname='${POSTGRES_DB}';" || true) | |
if [ -z "$DB_EXISTS" ]; then | |
echo "Database ${POSTGRES_DB} does not exist. Creating database…" | |
psql -v ON_ERROR_STOP=1 --host="$RDS_HOST" --username="$POSTGRES_USER" --dbname="postgres" \ | |
--command="CREATE DATABASE ${POSTGRES_DB};" | |
else | |
echo "Database ${POSTGRES_DB} already exists." | |
fi | |
echo "Granting privileges to user ${POSTGRES_USER}…" | |
psql -v ON_ERROR_STOP=1 --host="$RDS_HOST" --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" <<EOSQL | |
GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_USER}; | |
GRANT CREATE ON SCHEMA public TO ${POSTGRES_USER}; | |
EOSQL | |
unset PGPASSWORD | |
EOF_INIT | |
chmod +x init-data.sh | |
echo "Waiting for PostgreSQL to be reachable…" | |
until PGPASSWORD=$POSTGRES_PASSWORD psql --host "$RDS_HOST" --username "$POSTGRES_USER" --dbname postgres -c '\q' >/dev/null 2>&1; do | |
echo "PostgreSQL is unavailable – sleeping" | |
sleep 5 | |
done | |
echo "PostgreSQL is reachable. Running init-data.sh to initialize the database…" | |
./init-data.sh "$RDS_HOST" "$POSTGRES_USER" "$POSTGRES_PASSWORD" "$POSTGRES_DB" | |
echo "Creating docker-compose.yml file…" | |
cat > docker-compose.yml <<'EOF_COMPOSE' | |
services: | |
redis: | |
image: redis:6.2-alpine | |
restart: always | |
volumes: | |
- redis_storage:/data | |
healthcheck: | |
test: ["CMD", "redis-cli", "ping"] | |
interval: 5s | |
timeout: 5s | |
retries: 5 | |
n8n: | |
image: n8nio/n8n:latest | |
restart: always | |
environment: | |
- DB_TYPE=postgresdb | |
- DB_POSTGRESDB_HOST=${POSTGRES_HOST} | |
- DB_POSTGRESDB_PORT=5432 | |
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB} | |
- DB_POSTGRESDB_USER=${POSTGRES_USER} | |
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD} | |
- DB_POSTGRESDB_SSL=true | |
- EXECUTIONS_MODE=queue | |
- QUEUE_BULL_REDIS_HOST=redis | |
- QUEUE_HEALTH_CHECK_ACTIVE=true | |
- N8N_BASIC_AUTH_ACTIVE=true | |
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER} | |
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD} | |
- DB_POSTGRESDB_SSL_CA=/rds-ca.pem | |
- DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED=false | |
- N8N_HOST=${DOMAIN_NAME} | |
- N8N_PORT=5678 | |
- N8N_PROTOCOL=${N8N_PROTOCOL} | |
- N8N_WEBHOOK_URL=${N8N_WEBHOOK_URL} | |
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} | |
- N8N_EDITOR_BASE_URL=${N8N_EDITOR_BASE_URL} | |
- N8N_TRUSTED_PROXIES=${N8N_TRUSTED_PROXIES} | |
ports: | |
- "127.0.0.1:5678:5678" | |
volumes: | |
- n8n_storage:/home/node/.n8n | |
- ./rds-ca.pem:/rds-ca.pem | |
depends_on: | |
- redis | |
n8n-worker: | |
image: n8nio/n8n:latest | |
restart: always | |
command: worker | |
environment: | |
- DB_TYPE=postgresdb | |
- DB_POSTGRESDB_HOST=${POSTGRES_HOST} | |
- DB_POSTGRESDB_PORT=5432 | |
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB} | |
- DB_POSTGRESDB_USER=${POSTGRES_USER} | |
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD} | |
- DB_POSTGRESDB_SSL=true | |
- EXECUTIONS_MODE=queue | |
- QUEUE_BULL_REDIS_HOST=redis | |
- QUEUE_HEALTH_CHECK_ACTIVE=true | |
- DB_POSTGRESDB_SSL_CA=/rds-ca.pem | |
- DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED=false | |
- N8N_HOST=${DOMAIN_NAME} | |
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} | |
volumes: | |
- n8n_storage:/home/node/.n8n | |
- ./rds-ca.pem:/rds-ca.pem | |
depends_on: | |
- redis | |
ollama: | |
image: ollama/ollama:latest | |
restart: always | |
ports: | |
- "127.0.0.1:11434:11434" # keep private | |
volumes: | |
- ollama_models:/root/.ollama | |
environment: | |
- OLLAMA_NUM_THREADS=2 | |
- OLLAMA_MAX_LOADED_MODELS=1 | |
- OLLAMA_KEEP_ALIVE=300 | |
volumes: | |
n8n_storage: | |
redis_storage: | |
ollama_models: | |
EOF_COMPOSE | |
echo "Starting n8n services via docker-compose…" | |
# When using docker-compose-plugin on Amazon Linux 2023, the `docker compose` command | |
# and the legacy `docker-compose` wrapper are both provided. Using `docker compose` | |
# is recommended, but this wrapper retains compatibility. | |
docker compose up -d | |
### Step 4: Configure Nginx and SSL | |
echo "Creating initial Nginx configuration…" | |
NGINX_CONF="/etc/nginx/conf.d/n8n.conf" | |
sudo tee "$NGINX_CONF" >/dev/null <<EOF_NGINX | |
server { | |
server_name ${DOMAIN_OR_IP}; | |
location / { | |
proxy_pass http://127.0.0.1:5678; | |
proxy_set_header Host \$host; | |
proxy_set_header X-Real-IP \$remote_addr; | |
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | |
proxy_set_header X-Forwarded-Proto \$scheme; | |
# WebSocket support | |
proxy_http_version 1.1; | |
proxy_set_header Upgrade \$http_upgrade; | |
proxy_set_header Connection "upgrade"; | |
# Long-running requests | |
proxy_read_timeout 86400; | |
proxy_send_timeout 86400; | |
} | |
listen 80; | |
listen [::]:80; | |
} | |
EOF_NGINX | |
echo "Testing and reloading Nginx…" | |
sudo nginx -t && sudo systemctl reload nginx | |
echo "Obtaining SSL certificate with Certbot…" | |
CERTBOT_EMAIL=$(grep -E '^CERTBOT_EMAIL=' .env | cut -d '=' -f2) | |
if [[ "$DOMAIN_OR_IP" =~ ^[0-9.]+$ ]]; then | |
echo "Skipping Certbot — needs a real DNS name (got IP: $DOMAIN_OR_IP)." | |
else | |
sudo certbot --nginx -d "$DOMAIN_OR_IP" --agree-tos --non-interactive -m "$CERTBOT_EMAIL" | |
fi | |
if [[ "$DOMAIN_OR_IP" =~ ^[0-9.]+$ ]]; then | |
echo "Keeping HTTP-only Nginx config (no TLS for bare IP)." | |
else | |
echo "Final Nginx configuration with HTTPS enabled…" | |
sudo tee "$NGINX_CONF" >/dev/null <<EOF_FINAL | |
server { | |
if (\$host = ${DOMAIN_OR_IP}) { | |
return 301 https://\$host\$request_uri; | |
} | |
listen 80; | |
listen [::]:80; | |
server_name ${DOMAIN_OR_IP}; | |
return 404; | |
} | |
server { | |
server_name ${DOMAIN_OR_IP}; | |
location / { | |
proxy_pass http://127.0.0.1:5678; | |
proxy_set_header Host \$host; | |
proxy_set_header X-Real-IP \$remote_addr; | |
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; | |
proxy_set_header X-Forwarded-Proto \$scheme; | |
# WebSocket support | |
proxy_http_version 1.1; | |
proxy_set_header Upgrade \$http_upgrade; | |
proxy_set_header Connection "upgrade"; | |
# Long-running requests | |
proxy_read_timeout 86400; | |
proxy_send_timeout 86400; | |
} | |
listen 443 ssl; | |
ssl_certificate /etc/letsencrypt/live/${DOMAIN_OR_IP}/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN_OR_IP}/privkey.pem; | |
} | |
EOF_FINAL | |
fi | |
echo "Testing and reloading Nginx with new configuration…" | |
sudo nginx -t && sudo systemctl reload nginx | |
echo "✅ n8n installation and reverse proxy setup completed successfully." | |
echo "Access your n8n instance at: https://${DOMAIN_OR_IP} (use the basic‑auth credentials defined in the .env file)." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment