#!/bin/bash # To be run by user n8n to create the pod and containers with # n8n (main) + n8n Task Runners sidecar, bind-mounted data/files dirs, # and systemd user services. set -e # Environment variables POD_NAME='n8n_pod' N8N_CTR_NAME='n8n_ctr' RUNNERS_CTR_NAME='n8n_runners_ctr' # Images (version must match between n8n and runners) N8N_IMAGE='docker.io/n8nio/n8n:1.121.3' RUNNERS_IMAGE='docker.io/n8nio/runners:1.121.3' HOST_LOCAL_IP='127.0.0.1' N8N_HOST_PORT='8087' N8N_CONTAINER_PORT='5678' BIND_DIR="$HOME/.local/share/$POD_NAME" # Container /home/node/.n8n holds workflows, credentials, config N8N_DATA_DIR="$BIND_DIR/data" # Container /files for the “Read/Write Files from Disk” node N8N_FILES_DIR="$BIND_DIR/files" USER_SYSTEMD_DIR="$HOME/.config/systemd/user" # Timezone TZ_VALUE='Europe/Berlin' GENERIC_TZ_VALUE='Europe/Berlin' # Shared secret between n8n and runners RUNNERS_AUTH_TOKEN='xqw97zn23vlmqxawezor87b2o2xmn873z' # Optional JS allow-lists. # NOTE: imports are *disabled by default*; you can relax these later. NODE_BUILTIN_MODULES='crypto,fs,path,buffer,stream,url,util' # e.g. "moment,uuid" once added to the runners image NODE_EXTERNAL_MODULES='' # Prepare directories mkdir -p "$N8N_DATA_DIR" "$N8N_FILES_DIR" "$USER_SYSTEMD_DIR" # Fix permissions for n8n user (node:1000 in container) podman unshare chown 1000:1000 "$N8N_DATA_DIR" "$N8N_FILES_DIR" # Create pod if not yet existing if ! podman pod exists "$POD_NAME"; then podman pod create -n "$POD_NAME" \ -p "$HOST_LOCAL_IP:$N8N_HOST_PORT:$N8N_CONTAINER_PORT" echo "Pod '$POD_NAME' created (rc=$?)" else echo "Pod '$POD_NAME' already exists." fi # n8n main container # Remove any old container podman rm -f "$N8N_CTR_NAME" # New Container podman run -d --name "$N8N_CTR_NAME" --pod "$POD_NAME" \ -e TZ="$TZ_VALUE" \ -e GENERIC_TIMEZONE="$GENERIC_TZ_VALUE" \ -e N8N_PORT="$N8N_CONTAINER_PORT" \ -e N8N_PROTOCOL='http' \ -e N8N_HOST='localhost' \ -e WEBHOOK_URL="http://localhost:${N8N_HOST_PORT}" \ -e N8N_RESTRICT_FILE_ACCESS_TO='/files' \ -e N8N_RUNNERS_ENABLED='true' \ -e N8N_RUNNERS_MODE='external' \ -e N8N_RUNNERS_AUTH_TOKEN="$RUNNERS_AUTH_TOKEN" \ -e N8N_RUNNERS_BROKER_LISTEN_ADDRESS='0.0.0.0' \ -e N8N_NATIVE_PYTHON_RUNNER='false' \ -e QUEUE_HEALTH_CHECK_ACTIVE='true' \ -e N8N_METRICS='false' \ -v "$N8N_DATA_DIR:/home/node/.n8n:Z" \ -v "$N8N_FILES_DIR:/files:Z" \ "$N8N_IMAGE" echo "Container '$N8N_CTR_NAME' started (rc=$?)" # n8n Task Runners container (only for JS, not for Python) # Remove any old container podman rm -f "$RUNNERS_CTR_NAME" # New Container # Python runner is effectively disabled because N8N_NATIVE_PYTHON_RUNNER=false on n8n. podman run -d --name "$RUNNERS_CTR_NAME" --pod "$POD_NAME" \ -e TZ="$TZ_VALUE" \ -e GENERIC_TIMEZONE="$GENERIC_TZ_VALUE" \ -e N8N_RUNNERS_AUTH_TOKEN="$RUNNERS_AUTH_TOKEN" \ -e N8N_RUNNERS_TASK_BROKER_URI='http://127.0.0.1:5679' \ -e N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT='15' \ -e NODE_FUNCTION_ALLOW_BUILTIN="$NODE_BUILTIN_MODULES" \ -e NODE_FUNCTION_ALLOW_EXTERNAL="$NODE_EXTERNAL_MODULES" \ "$RUNNERS_IMAGE" echo "Container '$RUNNERS_CTR_NAME' started (rc=$?)" # Wait for API readiness (/health) CHECK_URL="http://$HOST_LOCAL_IP:$N8N_HOST_PORT" echo -n "Waiting for n8n API at $CHECK_URL ..." for attempt in $(seq 1 30); do if curl -fsS "$CHECK_URL" >/dev/null 2>&1; then echo "ready." break fi sleep 2 if [ "$attempt" -eq 30 ]; then echo "timeout error." >&2 exit 1 fi done # Generate systemd service files cd "$USER_SYSTEMD_DIR" podman generate systemd --name --new --files "$POD_NAME" echo "Generated systemd service files (rc=$?)" # Stop & remove live pod and containers podman pod stop --ignore --time 15 "$POD_NAME" podman pod rm -f --ignore "$POD_NAME" if podman pod exists "$POD_NAME"; then echo "ERROR: Pod $POD_NAME still exists." >&2 exit 1 else echo "Stopped & removed live pod $POD_NAME and containers." fi # Enable systemd user services systemctl --user daemon-reload # pod service (creates pod + containers) systemctl --user enable --now "pod-${POD_NAME}.service" systemctl --user is-enabled "pod-${POD_NAME}.service" systemctl --user is-active "pod-${POD_NAME}.service" echo "Enabled systemd service pod-${POD_NAME}.service (rc=$?)" echo "To view status: systemctl --user status pod-${POD_NAME}.service" echo "To view logs: journalctl --user -u pod-${POD_NAME}.service -f" # Optional: also manage containers individually (generated by podman generate systemd) systemctl --user enable --now "container-${N8N_CTR_NAME}.service" systemctl --user enable --now "container-${RUNNERS_CTR_NAME}.service" echo "To view n8n logs : journalctl --user -u container-${N8N_CTR_NAME}.service -f" echo "To view runners logs : journalctl --user -u container-${RUNNERS_CTR_NAME}.service -f" echo "n8n API is reachable at http://$HOST_LOCAL_IP:$N8N_HOST_PORT"