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.