From 68fbb1ed539c183036a08e56896c1f2ea3b174a8 Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 10 Jun 2026 21:52:41 +0200 Subject: [PATCH 1/2] [n8n] feat: Add n8n service with host-mounted backups path Adds n8n following the 3-file service convention (docker-compose.yml, .env.example, README.md). SQLite storage with a configurable backups folder bind-mounted at /backups using rslave propagation, so the same compose file can run on a host with NFS-backed storage or on a plain local directory, switching only via N8N_BACKUPS_PATH. Co-Authored-By: Claude Opus 4.8 (1M context) --- n8n/.env.example | 27 ++++++++++++++++ n8n/README.md | 72 ++++++++++++++++++++++++++++++++++++++++++ n8n/docker-compose.yml | 40 +++++++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 n8n/.env.example create mode 100644 n8n/README.md create mode 100644 n8n/docker-compose.yml diff --git a/n8n/.env.example b/n8n/.env.example new file mode 100644 index 0000000..75e2994 --- /dev/null +++ b/n8n/.env.example @@ -0,0 +1,27 @@ +# Base path for persistent n8n data (mounted at /data) +N8N_DATA=/path/to/n8n/data + +# UID:GID the container runs as. Must own ${N8N_DATA}/data and be able +# to write the backups folder (match it to the share owner where needed). +N8N_USER=1000:1000 + +# Host port mapped to the container's 5678 +N8N_PORT=5678 + +# Timezone (e.g., Europe/Madrid) +TZ=Etc/UTC + +# Public hostname and protocol used by n8n for URLs and cookies +N8N_HOST=n8n.example.com +N8N_PROTOCOL=https +N8N_SECURE_COOKIE=true + +# Encryption key for stored credentials (generate a strong random value) +N8N_ENCRYPTION_KEY=replace_me_with_a_long_random_string + +# Host path bind-mounted at /backups. +# - Host with network storage: point to an OS-level NFS automount (fstab +# with x-systemd.automount,nofail) so n8n boots without waiting for it, +# e.g. /mnt/backups-n8n +# - Host without it: any local directory, e.g. /opt/n8n/backups +N8N_BACKUPS_PATH=/opt/n8n/backups diff --git a/n8n/README.md b/n8n/README.md new file mode 100644 index 0000000..d0c8b52 --- /dev/null +++ b/n8n/README.md @@ -0,0 +1,72 @@ +# n8n + +Deploys [n8n](https://n8n.io/), a workflow automation tool. +Uses SQLite for storage and bind-mounts a host folder at +`/backups`. + +The same `docker-compose.yml` is meant to run as two +independent Dokploy applications on different hosts, +differing only in their environment variables: + +- **Host with network storage access:** `N8N_BACKUPS_PATH` + points to an OS-level NFS automount. +- **Host without it** (e.g. a remote VPS): `N8N_BACKUPS_PATH` + points to a plain local directory. + +## Environment Variables + +| Variable | Description | +|--------------------------|----------------------------------------------------------| +| `N8N_DATA` | Local path for persistent n8n data (mounted at `/data`) | +| `N8N_USER` | `UID:GID` the container runs as; must own `${N8N_DATA}/data` and be able to write the backups folder | +| `N8N_PORT` | Host port mapped to the container's `5678` | +| `TZ` | Timezone (also used as `GENERIC_TIMEZONE`) | +| `N8N_HOST` | Public hostname n8n serves on | +| `N8N_PROTOCOL` | `http` or `https` (used for URLs and cookies) | +| `N8N_SECURE_COOKIE` | `true` when serving over HTTPS, `false` otherwise | +| `N8N_ENCRYPTION_KEY` | Secret used to encrypt stored credentials | +| `N8N_BACKUPS_PATH` | Host path bind-mounted at `/backups` (NFS automount on the minipc, local dir on a VPS) | + +## Networks + +Connects to `dokploy-network` (external, managed by Dokploy). + +## Volumes + +- `${N8N_DATA}/data` → `/data` (bind mount, holds n8n's + SQLite DB and configuration; `HOME` is set to `/data`). +- `${N8N_BACKUPS_PATH}` → `/backups` (bind mount with + `rslave` propagation, so an on-demand NFS automount that + appears on the host *after* the container starts is still + visible inside it). + +## Notes + +- `${N8N_DATA}/data` on the host must be owned by the same + `UID:GID` set in `N8N_USER`, otherwise n8n will fail to + write its database. The backups folder must also be + writable by that UID (match `N8N_USER` to the owner of + the network share, or make the share group-writable). +- **Network-storage resilience:** mount the NFS share via + the OS, not via a Docker volume, using an fstab line with + `x-systemd.automount,nofail`, e.g.: + + ``` + :/ /mnt/backups-n8n nfs \ + defaults,nofail,x-systemd.automount,_netdev,vers=4.1 0 0 + ``` + + This way n8n boots immediately after a power outage even + if the storage server is slower to come up; autofs mounts + the share on first access and `rslave` propagation makes + it visible inside the running container. While the share + is down, backup operations fail cleanly instead of writing + to the host's local disk and being silently lost. +- Set `N8N_PROTOCOL=https` and `N8N_SECURE_COOKIE=true` + only when a reverse proxy terminates TLS in front of + n8n; for plain HTTP use `http` / `false`. +- Generate `N8N_ENCRYPTION_KEY` once and keep it stable — + rotating it makes existing stored credentials unreadable. +- `NODE_FUNCTION_ALLOW_BUILTIN=fs` enables the `fs` module + inside Function nodes (needed if workflows read/write + files under `/data` or `/backups`). diff --git a/n8n/docker-compose.yml b/n8n/docker-compose.yml new file mode 100644 index 0000000..1ea95bc --- /dev/null +++ b/n8n/docker-compose.yml @@ -0,0 +1,40 @@ +services: + n8n: + image: docker.n8n.io/n8nio/n8n:latest + container_name: n8n + restart: unless-stopped + user: ${N8N_USER} + security_opt: + - no-new-privileges:true + ports: + - "${N8N_PORT}:5678" + volumes: + - ${N8N_DATA}/data:/data + - type: bind + source: ${N8N_BACKUPS_PATH} + target: /backups + bind: + propagation: rslave + environment: + HOME: /data + TZ: ${TZ} + GENERIC_TIMEZONE: ${TZ} + N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY} + N8N_HOST: ${N8N_HOST} + N8N_PORT: 5678 + N8N_PROTOCOL: ${N8N_PROTOCOL} + N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE} + DB_SQLITE_POOL_SIZE: 5 + N8N_BLOCK_ENV_ACCESS_IN_NODE: "true" + NODE_FUNCTION_ALLOW_BUILTIN: fs + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + labels: + - com.centurylinklabs.watchtower.enable=true + +networks: + dokploy-network: + external: true From a69ac6ae775faeebd435d712e160f850b8658e8f Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 10 Jun 2026 22:19:21 +0200 Subject: [PATCH 2/2] [n8n] fix: Drop network declaration, fix markdownlint - Remove the dokploy-network declaration; Dokploy attaches its own network automatically on deploy (resolves the docs/compose mismatch flagged in review). - Add a language to the fstab fenced code block (MD040) and realign the env vars table (MD060). Co-Authored-By: Claude Opus 4.8 (1M context) --- n8n/README.md | 27 ++++++++++++++------------- n8n/docker-compose.yml | 4 ---- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/n8n/README.md b/n8n/README.md index d0c8b52..02e433b 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -15,21 +15,22 @@ differing only in their environment variables: ## Environment Variables -| Variable | Description | -|--------------------------|----------------------------------------------------------| -| `N8N_DATA` | Local path for persistent n8n data (mounted at `/data`) | -| `N8N_USER` | `UID:GID` the container runs as; must own `${N8N_DATA}/data` and be able to write the backups folder | -| `N8N_PORT` | Host port mapped to the container's `5678` | -| `TZ` | Timezone (also used as `GENERIC_TIMEZONE`) | -| `N8N_HOST` | Public hostname n8n serves on | -| `N8N_PROTOCOL` | `http` or `https` (used for URLs and cookies) | -| `N8N_SECURE_COOKIE` | `true` when serving over HTTPS, `false` otherwise | -| `N8N_ENCRYPTION_KEY` | Secret used to encrypt stored credentials | -| `N8N_BACKUPS_PATH` | Host path bind-mounted at `/backups` (NFS automount on the minipc, local dir on a VPS) | +| Variable | Description | +|----------------------|---------------------------------------------------------| +| `N8N_DATA` | Local path for persistent n8n data (mounted at `/data`) | +| `N8N_USER` | `UID:GID` the container runs as (see Notes for perms) | +| `N8N_PORT` | Host port mapped to the container's `5678` | +| `TZ` | Timezone (also used as `GENERIC_TIMEZONE`) | +| `N8N_HOST` | Public hostname n8n serves on | +| `N8N_PROTOCOL` | `http` or `https` (used for URLs and cookies) | +| `N8N_SECURE_COOKIE` | `true` when serving over HTTPS, `false` otherwise | +| `N8N_ENCRYPTION_KEY` | Secret used to encrypt stored credentials | +| `N8N_BACKUPS_PATH` | Host path bind-mounted at `/backups` (see Notes) | ## Networks -Connects to `dokploy-network` (external, managed by Dokploy). +No network is declared in the compose file — Dokploy +attaches its own network automatically on deploy. ## Volumes @@ -51,7 +52,7 @@ Connects to `dokploy-network` (external, managed by Dokploy). the OS, not via a Docker volume, using an fstab line with `x-systemd.automount,nofail`, e.g.: - ``` + ```text :/ /mnt/backups-n8n nfs \ defaults,nofail,x-systemd.automount,_netdev,vers=4.1 0 0 ``` diff --git a/n8n/docker-compose.yml b/n8n/docker-compose.yml index 1ea95bc..9463c9e 100644 --- a/n8n/docker-compose.yml +++ b/n8n/docker-compose.yml @@ -34,7 +34,3 @@ services: max-file: "3" labels: - com.centurylinklabs.watchtower.enable=true - -networks: - dokploy-network: - external: true