Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions n8n/.env.example
Original file line number Diff line number Diff line change
@@ -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
73 changes: 73 additions & 0 deletions n8n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# n8n

Deploys [n8n](https://n8n.io/), a workflow automation tool.
Uses SQLite for storage and bind-mounts a host folder at
`/backups`.
Comment on lines +1 to +5

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 (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

No network is declared in the compose file — Dokploy
attaches its own network automatically on deploy.

## 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.:

```text
<nas-host>:/<export-path> /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`).
36 changes: 36 additions & 0 deletions n8n/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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:
Comment on lines +7 to +11
- ${N8N_DATA}/data:/data
- type: bind
source: ${N8N_BACKUPS_PATH}
target: /backups
bind:
propagation: rslave
Comment on lines +9 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing networks: key causes the service to ignore dokploy-network.

The dokploy-network is declared at the file level (lines 38-40) but the service definition lacks a networks: key, so the container will connect to the default bridge network instead of dokploy-network as intended by the PR and documented in the README.

Additionally, the coding guidelines require the property order: networks > ports > volumes, but here ports and volumes appear before any networks key.

🔧 Proposed fix
     security_opt:
       - no-new-privileges:true
+    networks:
+      - dokploy-network
     ports:
       - "${N8N_PORT}:5678"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ports:
- "${N8N_PORT}:5678"
volumes:
- ${N8N_DATA}/data:/data
- type: bind
source: ${N8N_BACKUPS_PATH}
target: /backups
bind:
propagation: rslave
networks:
- dokploy-network
ports:
- "${N8N_PORT}:5678"
volumes:
- ${N8N_DATA}/data:/data
- type: bind
source: ${N8N_BACKUPS_PATH}
target: /backups
bind:
propagation: rslave
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@n8n/docker-compose.yml` around lines 9 - 17, Service is missing a networks
key so it won't attach to dokploy-network and property order violates
guidelines; add a networks: entry referencing dokploy-network above ports (i.e.,
place networks before ports and volumes) in the service definition so the
container attaches to dokploy-network and follows the required property order;
ensure the networks key lists dokploy-network exactly (matching the top-level
dokploy-network declaration).

Source: Coding guidelines

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
Loading