Dockerfile Patch & Legacy Script

Build a clean, immutable image with the new Dockerfile patch, compatible with every published n8n version. The downloadable patcher script is deprecated and should only be used when you absolutely cannot rebuild your container.

Recommended: build with the Dockerfile patch

Copy the Dockerfile snippet below, choose any upstream n8n tag, and run docker build. You get a predictable, auditable image with the license override baked in.

Legacy patcher (deprecated)

Keep this script only for frozen containers you can't rebuild. It still backs up LicenseManager.js and can be restored with sh ./patcher.sh restore.

Dockerfile patch
Recommended file to rebuild a patched n8n image for any version.
# Dockerfile patch to change license server URL and public key
#Change N8N_BASE_TAG as needed
ARG N8N_BASE_TAG=2.1.1
FROM n8nio/n8n:${N8N_BASE_TAG}

USER root

# ---- Build arguments ----
ARG NEW_URL="https://www.n8selfhosted.com"
ARG NEW_CERT="-----BEGIN PUBLIC KEY-----\nMIICsTCCAZmgAwIBAgIBATANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDExFNeSBM\naWNlbnNlIFNlcnZlcjAeFw0yNTEwMTYyMjE5MDFaFw0zNTEwMTYyMjE5MDFaMBwx\nGjAYBgNVBAMTEU15IExpY2Vuc2UgU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAvQKuPlPTTIdeCyj0CW4Z+sXX8b3G+hXTT8O8OgiXgW75UVDr\nsPhmC5dLyj6rQRjZqdNhaJz6FTYR0Ga93BS4nKSlCAq4k5gDprKuRiGTvPrPcBgE\n3OFc994QHU/lUUGHwT0SI3mw71rWS1FiklRKLbRYbFu1JUneRohTqzaY6SLklNEp\nlXPEzzHAYDahoJGeLhQA8IE5u6KxXIOx4TjntVU9Rhy1VeTshXimhtyRhp/aenaX\n4Lv+tQDecwYCw2OMeOUcg1Dmrev82E5BMSkTQW334Wu6PdSfZLHzm2wAAjgvEvNv\nG7jSI2jluZi8gYGtaiWqgj2kpYczWwC/LgrL9wIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQCwMQuZvWZTHjBir4Se9XzbF6lg3CkCa6aKeavWh3GrcQR5olB+c25yhPwH\nfcbJCz3j3eobmNDSFHKu77qN0l7pvkXoS79SuBE43xqrHiAc8MJsebFzh723Xu9t\nKrvHErlblcq8ZhLxFl4pOiXknVXBndB7Ic8xphTd2f2myPqP8w6VVEg2rVfqBgNz\nk//FkhpnXEdEdpExmCARR/3T0zbad5R9bZltwcRHpmF8Nty9Yx69lO+GsjjTwvjq\nKnpCQ0MlWPCdRkOZZpT6EQrAQhdMvhQfxCvsGHvOAZoDWdFKIUqyhlu2o+XCk7OJ\nhNCqS2JdxHm/+CbsPJVonIlW2dTb\n-----END PUBLIC KEY-----"

# ---- Create and apply patch in one step ----
RUN TARGET_FILE=$(ls /usr/local/lib/node_modules/n8n/node_modules/.pnpm/@n8n_io+license-sdk@*/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js 2>/dev/null | head -n 1) && \
    if [ -z "$TARGET_FILE" ]; then \
        echo "ERROR: LicenseManager.js not found!"; \
        find /usr/local/lib/node_modules/n8n -name "*.js" | grep -i license | head -n 10; \
        exit 1; \
    fi && \
    echo "Found: $TARGET_FILE" && \
    cp "$TARGET_FILE" "${TARGET_FILE}.backup" && \
    node -e " \
        const fs = require('fs'); \
        const file = process.argv[1]; \
        const url = process.argv[2]; \
        const cert = process.argv[3]; \
        let content = fs.readFileSync(file, 'utf8'); \
        content = content.replace(/https:\/\/license\.n8n\.io/g, url); \
        const formattedCert = cert.replace(/\\\\n/g, '\\n'); \
        content = content.replace(/-----BEGIN PUBLIC KEY-----[\\s\\S]*?-----END PUBLIC KEY-----/g, formattedCert); \
        fs.writeFileSync(file, content, 'utf8'); \
        console.log('✓ Patched'); \
    " "$TARGET_FILE" "$NEW_URL" "$NEW_CERT" && \
    if grep -q "$NEW_URL" "$TARGET_FILE"; then \
        echo "✓ Verified"; \
    else \
        echo "✗ Failed"; \
        exit 1; \
    fi && \
    chown node:node "$TARGET_FILE" && \
    chmod 644 "$TARGET_FILE"

# ---- Final setup ----
USER node
WORKDIR /home/node
patcher.shDeprecated
Legacy script kept for frozen containers you cannot rebuild yet.
#!/bin/bash
set -e # Exit the script if a command fails

# --- ⬇️ CONFIGURATION ⬇️ ---
# Paste your new MULTILINE certificate between the 'EOF' lines.
# DO NOT touch the \`cat <<'EOF'\` line or the final \`EOF\` line.
NEW_CERTIFICATE_BLOCK=$(cat <<'EOF'
MIICsTCCAZmgAwIBAgIBATANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDExFNeSBM
aWNlbnNlIFNlcnZlcjAeFw0yNTEwMTYyMjE5MDFaFw0zNTEwMTYyMjE5MDFaMBwx
GjAYBgNVBAMTEU15IExpY2Vuc2UgU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAvQKuPlPTTIdeCyj0CW4Z+sXX8b3G+hXTT8O8OgiXgW75UVDr
sPhmC5dLyj6rQRjZqdNhaJz6FTYR0Ga93BS4nKSlCAq4k5gDprKuRiGTvPrPcBgE
3OFc994QHU/lUUGHwT0SI3mw71rWS1FiklRKLbRYbFu1JUneRohTqzaY6SLklNEp
lXPEzzHAYDahoJGeLhQA8IE5u6KxXIOx4TjntVU9Rhy1VeTshXimhtyRhp/aenaX
4Lv+tQDecwYCw2OMeOUcg1Dmrev82E5BMSkTQW334Wu6PdSfZLHzm2wAAjgvEvNv
G7jSI2jluZi8gYGtaiWqgj2kpYczWwC/LgrL9wIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQCwMQuZvWZTHjBir4Se9XzbF6lg3CkCa6aKeavWh3GrcQR5olB+c25yhPwH
fcbJCz3j3eobmNDSFHKu77qN0l7pvkXoS79SuBE43xqrHiAc8MJsebFzh723Xu9t
KrvHErlblcq8ZhLxFl4pOiXknVXBndB7Ic8xphTd2f2myPqP8w6VVEg2rVfqBgNz
k//FkhpnXEdEdpExmCARR/3T0zbad5R9bZltwcRHpmF8Nty9Yx69lO+GsjjTwvjq
KnpCQ0MlWPCdRkOZZpT6EQrAQhdMvhQfxCvsGHvOAZoDWdFKIUqyhlu2o+XCk7OJ
hNCqS2JdxHm/+CbsPJVonIlW2dTb
EOF
)
# --- ⬆️ CONFIGURATION ⬆️ ---


# --- DO NOT EDIT BELOW THIS LINE ---

# Function to display usage instructions
usage() {
  echo "🚨 Incorrect usage."
  echo "Run the script in one of the following ways:"
  echo " $0 patch   (to apply the new certificate defined in the script)"
  echo " $0 restore  (to restore the certificate from the backup)"
  exit 1
}

# Verify that an action (patch or restore) was provided
ACTION=$1

if [ "$ACTION" != "patch" ] && [ "$ACTION" != "restore" ]; then
  usage
fi

# --- MAIN LOGIC ---

# Detect if we are INSIDE a Docker container
if [ -f /.dockerenv ]; then
  echo "✅ Detected: Running main logic INSIDE the container."

  FILE_PATH=$(find / -type f -name "LicenseManager.js" 2>/dev/null | head -n 1)
  if [ -z "$FILE_PATH" ]; then
    echo "❌ Error: Target file not found inside the container."
    exit 1
  fi
  echo "✅ Found file: $FILE_PATH"

  case "$ACTION" in
    patch)
      echo "Starting operation: PATCH"

      BACKUP_PATH="$FILE_PATH.bak"
      if [ ! -f "$BACKUP_PATH" ]; then
        echo "✅ Creating backup of the original file to: $BACKUP_PATH"
        cp "$FILE_PATH" "$BACKUP_PATH"
      else
        echo "✅ Backup already exists, a new one will not be created."
      fi
      
      echo "Modifying the certificate in the file..."
      awk -v new_cert="$NEW_CERTIFICATE_BLOCK" '{
        if ($!0 !~ /-----BEGIN CERTIFICATE-----/) {
          print $!0
          print new_cert
          in_block = 1
          next
        }
        if ($!0 !~ /-----END CERTIFICATE-----/) {
          print $!0
          in_block = 0
          next
        }
        if (!in_block) {
          print $!0
        }
      }' "$FILE_PATH" > "$FILE_PATH.tmp" && mv "$FILE_PATH.tmp" "$FILE_PATH"

      echo "✅ Patch applied! Restart n8n for the changes to take effect."

      awk -v from='this.config.server=this.config.server??"https://license.n8n.io/v1"' \
        -v to='this.config.server="https://patcher.n8selfhosted.com"' '
      {
        while ((pos = index($!0, from)) > 0) {
          $!0 = substr($!0, 1, pos - 1) to substr($!0, pos + length(from))
        }
        print
      }' "$FILE_PATH" > "$FILE_PATH.tmp" && mv "$FILE_PATH.tmp" "$FILE_PATH"

      echo "✅ Patch License URL applied! https://patcher.n8selfhosted.com"
      
      ;;
    restore)
      echo "Starting operation: RESTORE"
      BACKUP_PATH="$FILE_PATH.bak"
      if [ -f "$BACKUP_PATH" ]; then
        echo "Restoring the original file from backup..."
        cp "$BACKUP_PATH" "$FILE_PATH"
        echo "✅ Original file restored! Restart n8n for the changes to take effect."
      else
        echo "❌ Error: Backup file not found at '$BACKUP_PATH'. Cannot restore."
        exit 1
      fi
      ;;
  esac
else
  echo "✅ Detected: Running on the host machine (outside the container)."
  
  if ! command -v docker >/dev/null 2>&1; then
    echo "❌ Error: The 'docker' command was not found. Ensure Docker is installed and accessible in your PATH."
    exit 1
  fi
  
  read -p "Please enter the name or ID of the n8n container: " CONTAINER_NAME

  if [ -z "$CONTAINER_NAME" ]; then
    echo "❌ Error: No container name was entered."
    exit 1
  fi

  echo "Verifying if container '$CONTAINER_NAME' is running..."
  if ! docker ps -f "name=$CONTAINER_NAME" --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then
    echo "❌ Error: Container '$CONTAINER_NAME' is not found or is not running."
    exit 1
  fi

  echo "Searching for LicenseManager.js inside the container..."
  FILE_PATH=$(docker exec "$CONTAINER_NAME" find / -type f -name "LicenseManager.js" 2>/dev/null | head -n 1)
  if [ -z "$FILE_PATH" ]; then
    echo "❌ Error: Target file 'LicenseManager.js' not found inside container '$CONTAINER_NAME'."
    exit 1
  fi
  echo "✅ Found file inside container: $FILE_PATH"
  
  SHELL_COMMAND=""
  echo "Searching for compatible shells inside the container..."
  
  if docker exec "$CONTAINER_NAME" which bash >/dev/null 2>&1; then
    SHELL_COMMAND="bash"
    echo " -> Using /bin/bash."
  elif docker exec "$CONTAINER_NAME" which sh >/dev/null 2>&1; then
    SHELL_COMMAND="sh"
    echo " -> Using /bin/sh as fallback."
  else
    echo "❌ Error: Neither 'bash' nor 'sh' could be found inside container '$CONTAINER_NAME'."
    exit 1
  fi

  SCRIPT_NAME=$(basename "$0")
  CONTAINER_SCRIPT_PATH="/tmp/$SCRIPT_NAME"

  echo "Copying script to '$CONTAINER_NAME:$CONTAINER_SCRIPT_PATH'..."
  docker cp "$0" "$CONTAINER_NAME:$CONTAINER_SCRIPT_PATH"

  echo "Executing the script inside the container using command: $SHELL_COMMAND $CONTAINER_SCRIPT_PATH $ACTION"
  docker exec "$CONTAINER_NAME" $SHELL_COMMAND "$CONTAINER_SCRIPT_PATH" "$ACTION"
  if [ $? -ne 0 ]; then
    echo "❌ Error: Failed to execute the script inside the container."
    exit 1
  fi

  echo "Cleaning up the script from the container..."
  docker exec "$CONTAINER_NAME" rm "$CONTAINER_SCRIPT_PATH"

  echo "✅ Operation completed in the container. You must now restart the container for changes to load:"
  echo "  docker restart $CONTAINER_NAME"
fi
    Dockerfile Patch | Legacy Patcher for n8n