Skip to content

Instantly share code, notes, and snippets.

@minaairsupport
Created October 14, 2025 13:33
Show Gist options
  • Save minaairsupport/cc52c0313d82c3d736cab37ea02a289c to your computer and use it in GitHub Desktop.
Save minaairsupport/cc52c0313d82c3d736cab37ea02a289c to your computer and use it in GitHub Desktop.
#!/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