n8n + ffmpeg
apk add ffmpeg doesn't work in the n8n image anymore
Since n8n 2.1.0 the official Docker image ships without a package manager, so the one-line Dockerfile everyone copied exits with apk: not found. n8n Cloud never had ffmpeg at all. Here is what changed, the Dockerfile that still works for self-hosters, and the no-image route for everyone else.
What changed
The official image has shipped without apk since 2.1.0 (December 2025). It is a hardened Alpine with the package manager removed, not distroless, so the container still has a shell but nothing to install with. The symptom is the same everywhere:
Step 3/4 : RUN apk add --no-cache ffmpeg
/bin/sh: apk: not found
ERROR: process "/bin/sh -c apk add --no-cache ffmpeg" did not complete successfully: exit code: 127Two more things landed with 2.0: the Execute Command node is disabled by default in self-hosted instances, and on n8n Cloud it does not exist. So even with ffmpeg in the image you need to re-enable the node before a workflow can call it (the 2.0 migration notes cover the NODES_EXCLUDE setting).
Self-hosted: the Dockerfile that still builds
Copy apk back in from a plain Alpine stage of the same Alpine version as the n8n tag, install, and drop back to the node user:
FROM alpine:3.24 AS alpine
FROM docker.n8n.io/n8nio/n8n:latest
COPY --from=alpine /sbin/apk /sbin/apk
COPY --from=alpine /usr/lib/libapk.so* /usr/lib/
USER root
RUN apk add --no-cache ffmpeg
USER node- Built on 2026-09-17 against n8n 2.39.6: ffmpeg 8.1.2, n8n still starts as
node, the image grows from 270 to 313 MB. - The Alpine version has to match the one inside the n8n image. Check with
cat /etc/os-releasein a running container before you pick the first line. - It breaks the day n8n bumps its base image. Pin the n8n tag and budget a rebuild for every upgrade.
- Then re-enable Execute Command, keep it off the public internet, and write the ffmpeg commands yourself.
Fine for a home lab. Less fine when the video step is the only reason you own a custom image.
n8n Cloud, or no image to maintain: one HTTP call
The step after the video generator is boring and always the same: make the clip 9:16, put the channel logo on it, fix the loudness, maybe burn in captions. Brand-Pipe does exactly that behind one HTTP Request node, so it works on n8n Cloud, on Hostinger, on a locked-down instance, anywhere the node can make a POST.
curl -X POST https://api.brand-pipe.com/v1/process \
-H "X-API-Key: bp_your_key" \
-H "Content-Type: application/json" \
-d '{"video_url": "https://example.com/raw.mp4",
"logo_url": "https://example.com/logo.png",
"format": "vertical"}'The answer is a JSON envelope with a signed download link; the ready-made workflow turns that into binary data for your upload node.
- Import n8n-brand-pipe.json (Manual Trigger, Set, HTTP Request, IF, download).
- Create a Header Auth credential named Brand-Pipe API Key: header
X-API-Key, value your key. - Replace the Set video node with your generator's output and connect the binary output to YouTube, TikTok or storage. n8n-brand-pipe-youtube.json is the scheduled version with a YouTube Shorts upload, shipped disabled so you can test before the OAuth setup.
The loudness part is the one that bites. A 9-second GoPro clip with wind on a covered mic measured -70 LUFS, basically silence. Plain loudnorm pulled it up to -12.3 LUFS, which is 58 dB of hiss. Brand-Pipe measures first and leaves anything below -50 LUFS alone. If you normalise with ffmpeg yourself, add that check.
Tips that apply to any video API in n8n
- Turn on Never Error in the HTTP Request node options. Otherwise a 4xx throws and your IF node never sees the error message.
- Retry On Fail with a paid API can charge you twice when only the response got lost. Send an
Idempotency-Keyheader with{{ $execution.id }}-{{ $itemIndex }}. With just the execution id every item in the run gets the same key. - For longer clips don't keep the request open. Submit a job, put a Wait node on Resume on webhook call with method POST (the default is GET) and send
{{ $execution.resumeUrl }}as callback URL. Set a wait limit, or a lost callback parks the execution forever. n8n-brand-pipe-jobs.json is that pattern againstPOST /v1/jobs. - On the download node set Response Format to File. Autodetect works when the host sends a video content type; not every host does.
Pricing
25 credits free, no card. One credit per video, two if it burns in captions, then €9 for 250 credits. Credits don't expire. Failed renders are refunded automatically (except the rare one that hits the encode time limit). Sync is up to 60 s of output, jobs up to 10 min, 200 MB per source. Renders run in Frankfurt, files are stored in the EU and deleted after 24 h.
Questions or a case that doesn't fit? support@brand-pipe.com