File size: 11,556 Bytes
3a4589a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# n8n + FFmpeg β Production Dockerfile
# n8n version : 1.90.0 (latest stable 1.x as of April 2026)
# Base : Official n8nio/n8n:1.90.0 (Alpine-based, Node.js 20)
# Target : 1 CPU / 2 GB RAM (Railway / CloudStation)
#
# WHY 1.90.0 AND NOT 2.x:
# n8n 2.0 disabled ExecuteCommand and LocalFileTrigger nodes BY DEFAULT.
# Your FFmpeg Assembly workflow uses Execute Command nodes for every
# phase (FFprobe, Phase1, Phase2, Assembly A/B/C). Using 2.x would break
# the entire Assembly pipeline without a major workflow rewrite.
# 1.90.0 is the latest stable 1.x release β safe, production-tested,
# with all the bug fixes from 1.88 and 1.89.
#
# CHANGES FROM YOUR OLD DOCKERFILE (1.88.0):
# 1. Pinned to 1.90.0 β fixes editor lock bug + task runner improvements
# 2. N8N_RUNNERS_ENABLED=true β task runner support (stable in 1.90)
# 3. NODE_OPTIONS heap capped at 512MB β was uncapped β OOM kills
# 4. EXECUTIONS_PROCESS=main β no worker fork β saves ~200MB RAM
# 5. N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true β fixes editor lock
# 6. WEBHOOK_URL correctly set β fixes OAuth callback URI mismatch
# 7. N8N_EDITOR_BASE_URL set β fixes redirect URI shown in credentials UI
# 8. N8N_ENCRYPTION_KEY as explicit env β credentials survive restarts
# 9. Execution pruning enabled β SQLite stays small
# 10. Healthcheck uses correct /healthz endpoint
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
FROM n8nio/n8n:1.90.0
# ββ Root only for package installation βββββββββββββββββββββββββββββββββββ
USER root
# ββ Install system dependencies in a single layer ββββββββββββββββββββββββ
#
# ffmpeg β video encoding: zoompan, drawtext, libx264, aac, concat
# wget β Phase 1: download images from imgbb/external URLs
# fontconfig β fc-list: font path discovery for drawtext subtitle filter
# font-dejavu β DejaVuSans-Bold.ttf: used in Assembly B/C subtitle burn
# ca-certificates β HTTPS requests to Google APIs, imgbb without SSL errors
#
# Alpine's ffmpeg package includes: libx264, aac, wav/pcm, png codecs,
# zoompan filter, drawtext filter, thumbnail filter β everything the
# Assembly workflow needs. Codecs not used (libvpx, x265 etc.) are compiled
# in but do not consume RAM until invoked.
RUN apk add --no-cache \
ffmpeg \
wget \
fontconfig \
font-dejavu \
ca-certificates \
&& fc-cache -f \
&& echo "=== BUILD VERIFY ===" \
&& ffmpeg -version 2>&1 | head -1 \
&& ffprobe -version 2>&1 | head -1 \
&& fc-list | grep -i "DejaVu" | head -2 \
&& echo "=== DONE ==="
# ββ FFmpeg working directory ββββββββββββββββββββββββββββββββββββββββββββββ
# Assembly workflow writes to /tmp/sfcm/{run_id}/
# sticky bit allows node user to create subdirectories
RUN mkdir -p /tmp/sfcm \
&& chmod 1777 /tmp/sfcm \
&& chown node:node /tmp/sfcm
# ββ Back to unprivileged node user ββββββββββββββββββββββββββββββββββββββββ
USER node
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β Memory
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Cap Node.js JS heap at 512MB.
# Without this, n8n defaults to ~1.5GB on a 2GB system.
# During FFmpeg zoompan (Phase 2), FFmpeg itself uses 400-500MB.
# Total without cap: 1500 + 500 = OOM. Total with cap: 512 + 500 = 1012MB.
ENV NODE_OPTIONS="--max-old-space-size=512"
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β n8n Execution Engine
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Run executions in the main process β no worker subprocess spawning.
# Saves ~150-200MB vs forked worker mode. Essential on 2GB RAM.
ENV EXECUTIONS_PROCESS=main
# Task runners: stable in 1.90, required for Code node isolation.
# Internal mode (default) β no separate runner container needed.
ENV N8N_RUNNERS_ENABLED=true
ENV N8N_RUNNERS_MODE=internal
# Store binary file data on disk, not in SQLite.
# Without this, video files (~50-200MB) pass through the database β crash.
ENV N8N_DEFAULT_BINARY_DATA_MODE=filesystem
# Prevent the "editor locked" bug.
# This happens when n8n cannot write its settings file on startup.
ENV N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β Execution Pruning
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Without pruning, SQLite grows forever β disk full β all workflows fail.
# Keep 72 hours of history, max 200 executions stored at once.
ENV EXECUTIONS_DATA_PRUNE=true
ENV EXECUTIONS_DATA_MAX_AGE=72
ENV EXECUTIONS_DATA_PRUNE_MAX_COUNT=200
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β Network / OAuth (THE GOOGLE CREDENTIAL FIX)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ββ WEBHOOK_URL βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# THE most important setting. Set this to your actual public URL.
# This is the root cause of your Google credential expiry issue:
#
# If WEBHOOK_URL is wrong or missing:
# β n8n shows "http://localhost:5678/rest/oauth2-credential/callback"
# as the redirect URI in the credentials screen
# β You register a DIFFERENT URL in Google Cloud Console
# β OAuth handshake fails silently on first refresh attempt
# β n8n falls back to stored token which expires after 7 days (Testing mode)
# or 1 hour (if no valid refresh token was ever stored)
# β You see "credential expired" every N hours/days
#
# FIX: Set WEBHOOK_URL to your exact Railway/CloudStation domain.
# Override this at deploy time via your hosting platform's env vars.
# Do NOT hardcode the real URL here if this file is in a git repo.
ENV WEBHOOK_URL=https://your-n8n-domain.up.railway.app
# Must match WEBHOOK_URL exactly.
# n8n uses this to build the OAuth redirect URI shown in the credentials
# setup modal β the one you paste into Google Cloud Console.
ENV N8N_EDITOR_BASE_URL=https://your-n8n-domain.up.railway.app
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β Credential Encryption Key
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# CRITICAL: Without a fixed encryption key, n8n generates a NEW random key
# on every container restart. All saved credentials become unreadable.
# You then see "could not decrypt credentials" β which looks like expiry but
# is actually a key rotation problem.
#
# HOW TO GENERATE: Run this once locally:
# openssl rand -hex 32
# Copy the output. Set it as N8N_ENCRYPTION_KEY in Railway/CloudStation
# environment variables. Replace the placeholder below with your key,
# OR better: leave the placeholder here and set the real value only in
# your hosting platform's secret manager (never commit the real key).
ENV N8N_ENCRYPTION_KEY=REPLACE_WITH_YOUR_32_BYTE_HEX_KEY
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β Timezone + Port
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Pakistan Standard Time (UTC+5) β affects Schedule Trigger node timing
ENV GENERIC_TIMEZONE=Asia/Karachi
ENV TZ=Asia/Karachi
ENV N8N_PORT=5678
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# ENVIRONMENT VARIABLES β Reduce Noise
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ENV N8N_DIAGNOSTICS_ENABLED=false
ENV N8N_VERSION_NOTIFICATIONS_ENABLED=false
ENV N8N_LOG_LEVEL=info
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PORT + HEALTHCHECK
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
EXPOSE 5678
# /healthz is the correct health endpoint for n8n 1.x
# start-period=90s gives n8n time to initialize SQLite and load workflows
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \
CMD wget -qO- http://localhost:5678/healthz || exit 1
# The n8nio/n8n base image already sets:
# ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
# CMD ["n8n"]
# No override needed.
|