n8n Setup, Deployment Strategies, and Workflow Fundamentals
Self-host n8n with Docker Compose, deploy to cloud infrastructure, and master core execution models, item lists, and JSON data transformations.
Chapter Objectives
- Deploy self-hosted n8n with Docker Compose, persistent volumes, and PostgreSQL
- Evaluate deployment tradeoffs between n8n Cloud and self-hosted infrastructure
- Master n8n internal data model: JSON structures, item lists, and binary data
- Use pin data for rapid node-by-node debugging and testing
- Build an end-to-end conditional routing workflow with Webhooks, IF nodes, and Discord alerts
n8n Setup, Deployment Strategies, and Workflow Fundamentals
Before assembling autonomous AI agents that make multi-tool decisions, you must establish a reliable runtime environment and master n8n foundational execution mechanics. This chapter covers the architectural trade-offs between managed cloud and self-hosted Docker instances, breaks down the internal array-of-items data contract, and walks through building your first production automation from scratch.
1. Running n8n: Self-Hosted vs. n8n Cloud
In the n8n ecosystem, deployment choices have distinct technical and operational implications:
- n8n Cloud: The official managed SaaS platform hosted by the n8n core team. Servers, SSL certificates, database migrations, backups, and execution queues are managed for you.
- Self-Hosting: You operate the n8n container on infrastructure you control: a bare-metal server, an AWS EC2 instance, a private homelab, or a cloud VPS provider (such as DigitalOcean, Render, or Hetzner).
VPS Hosting is Classified as Self-Hosting
Deploying n8n on a remote cloud VPS provider is still considered self-hosting because you remain the systems administrator. You manage Docker containers, environment variables, SSL certificates, and database backups.
Deployment Trade-Off Matrix
| Feature | n8n Cloud | Self-Hosted (Docker on VPS) |
|---|---|---|
| Setup Time | Under 2 minutes | 15 to 45 minutes |
| Maintenance Burden | Zero maintenance: fully managed | OS patches, Docker updates, database backups |
| Pricing Model | Tiered subscription based on workflow executions | Flat VPS compute cost (5 to 20/month) with zero software fees |
| Execution Limits | Pinned to subscription tier | Unlimited workflow executions |
| Data Privacy & Compliance | Data processed in EU cloud data centers | 100% on-premise: complies with HIPAA, GDPR, SOC 2 |
| Network Access | Public webhooks with managed domains | Direct access to internal private VPC subnets & databases |
| Recommended For | Agencies, rapid prototypes, non-dev teams | Systems engineers, high-volume AI agent loops |
2. Production Docker Compose Configuration
For self-hosted production setups, running n8n alongside a dedicated PostgreSQL database container is the enterprise standard. While SQLite is suitable for rapid local evaluation, PostgreSQL provides the connection pooling and transaction isolation required for concurrent agent loops.
Here is a production-hardened docker-compose.yml manifest:
version: '3.8'
services:
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=replace_with_strong_database_password
- POSTGRES_DB=n8n
volumes:
- postgres_storage:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U n8n -d n8n"]
interval: 5s
timeout: 5s
retries: 10
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
# Database Backend
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=replace_with_strong_database_password
# Security & Encryption Key
- N8N_ENCRYPTION_KEY=generate_a_random_32_character_hex_key
- N8N_HOST=n8n.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
# Webhook Configuration
- WEBHOOK_URL=https://n8n.yourdomain.com/
# Timezone
- GENERIC_TIMEZONE=America/New_York
# Execution Data Pruning (keeps database lightweight)
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=168 # 7 days in hours
volumes:
- n8n_storage:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_storage:
n8n_storage:
Critical Environment Variables
N8N_ENCRYPTION_KEY: A persistent cryptographic key used to encrypt saved credentials (API keys, OAuth tokens) in the database. Store this key in a secure password manager: if this key is lost, all encrypted credentials become permanently unrecoverable.WEBHOOK_URL: The fully qualified public URL where third-party webhooks (Stripe, GitHub, Telegram) deliver incoming HTTP events.EXECUTIONS_DATA_PRUNE: In high-throughput environments, storing millions of successful execution logs exhausts disk space. SettingEXECUTIONS_DATA_PRUNE=trueautomatically discards successful logs older than a designated age (e.g. 168 hours) while preserving error logs for investigation.
3. n8n Fundamentals: Workflows, Nodes, and Data Flow
The n8n workflow editor is an infinite 2D canvas. Navigating efficiently accelerates development:
- Pan the Canvas: Hold
Spacebarand drag, or holdCtrl/Cmdand scroll. - Zoom: Hold
Ctrl/Cmdand scroll mouse wheel, or use+/-controls. - Select Multiple Nodes: Hold
Shiftand drag a bounding box over the nodes. - Tidy Up: Press
Shift + Ato automatically align all nodes and straighten connection splines. - Copy Nodes as JSON: Select any node or group and press
Ctrl + C(Cmd + C). The full configuration is copied to your clipboard as JSON.
The Five Core Building Blocks
[ Trigger Node ] ───> [ Transformation Node ] ───> [ Condition Node (IF) ] ───> [ Action Node ]
│
└───> [ Alternative Action ]
- Workflow: The complete pipeline representing an automated process.
- Trigger: The event that initiates execution (Webhook, Form Submission, Interval, Cron Schedule).
- Nodes: Discrete processing stations that transform data, invoke APIs, or query models.
- Expressions: JavaScript snippets wrapped in
{{ ... }}that extract upstream values dynamically. - Conditions & Routers: Branching nodes (IF, Switch) that route execution paths based on boolean evaluations.
4. The Underlying Data Model: The Item Array Contract
In n8n, data passed between nodes is always structured as an array of objects, where each element contains a json property and an optional binary property:
[
{
"json": {
"id": 101,
"email": "sarah@example.com",
"company": "Acme Corp",
"tier": "enterprise"
},
"binary": {}
},
{
"json": {
"id": 102,
"email": "marcus@example.com",
"company": "Beta LLC",
"tier": "starter"
},
"binary": {}
}
]
Automatic Item-Level Looping (The Conveyor Belt)
Because nodes process item arrays natively, downstream nodes execute once for every item in the array automatically. You do not need to construct explicit for loops. If an upstream database query returns 10 rows, a connected Slack node will dispatch 10 sequential messages unless an aggregation node combines them into a single summary item first.
Expression Syntax and Dynamic Variables
{{ $json.field_name }}: Resolves the field from the immediate predecessor node for the current item.{{ $('NodeName').item.json.field_name }}: Retrieves a field from an explicit upstream node namedNodeName.{{ $now.toISO() }}: Emits the current execution timestamp in ISO 8601 format (2026-09-15T14:30:00.000Z).{{ $now.format('yyyy-MM-dd') }}: Formats the current date using Luxon date expressions (2026-09-15).
// String interpolation in node parameters
"Hello {{ $json.first_name }}, your support ticket #{{ $json.ticket_id }} has been received."
// Ternary logic inside an expression
"Priority: {{ $json.tier === 'enterprise' ? 'URGENT' : 'STANDARD' }}"
Expression Case Sensitivity
Field names in n8n expressions are strictly case-sensitive. If an incoming webhook sends {{ $json.UserID }}, querying {{ $json.userId }} evaluates to undefined. Always inspect the Schema panel on the left side of the node editor to confirm key casing.
5. Practical Tutorial: Building a Production Support Request Notifier
Let's build a functional production automation from a blank canvas:
- Customers submit a ticket via an n8n-hosted web form.
- An IF condition evaluates whether the ticket is flagged as urgent.
- Urgent tickets dispatch an immediate formatted alert to a Discord channel.
- Non-urgent tickets route to a No-Op placeholder node for standard queue processing.
[ On Form Submission ] (Trigger)
│
▼
[ IF Node ] (Is this urgent?)
├── True Path ───> [ Discord Send Message ] (Posts to #support-alerts)
└── False Path ──> [ No-Op Node ] (Placeholder)
Step 1: Create the Form Trigger Node
- In your n8n workspace, click Create Workflow and name it
Support Request Notifier. - Add the On form submission trigger node. Configure:
- Form Title:
Submit a Support Request - Form Description:
Describe your issue and indicate whether immediate escalation is required.
- Form Title:
- Add two form fields:
- Field 1:
describe_the_issue(Type: Text, Required: true) - Field 2:
is_this_urgent(Type: Dropdown, Options:yes,no, Required: true)
- Field 1:
- Click Test step. Open the generated Test URL in a browser, submit a sample ticket (
is_this_urgent: "yes"), and observe the structured JSON payload appear in n8n.
Step 2: The Pin Data Productivity Secret
In the upper right corner of the Form node's output pane, click the Pin Data icon (the pushpin). Pinned data allows downstream nodes to be developed and tested instantly without having to submit new web forms on every canvas tweak. Pinned data applies only within the editor and is ignored during live production runs.
Step 3: Configure the IF Conditional Node
- Add an IF node connected to the Form trigger.
- Set condition:
- Value 1:
{{ $json.is_this_urgent }} - Operation:
String is equal to - Value 2:
yes
- Value 1:
- Connect a No-Op (No Operation) node to the False output port, naming it
Not Urgent.
Step 4: Configure Discord Integration
- On your Discord server, create a
#support-alertschannel and generate a Bot Token via the Discord Developer Portal. - Add the Discord node to the True branch of the IF node.
- Configure the node parameters:
- Resource:
Message - Operation:
Send - Channel ID: Select
#support-alerts - Message:
- Resource:
🚨 **URGENT SUPPORT REQUEST ESCALATION**
**Issue Description:**
{{ $json.describe_the_issue }}
**Urgent Flag:** {{ $json.is_this_urgent }}
**Received Timestamp:** {{ $now.format('cccc, LLLL d, yyyy HH:mm') }}
Step 5: Test and Activate
- Click Execute step on the Discord node to verify the message appears in Discord.
- Toggle the Active switch in the upper right corner of the canvas to ON.
- Switch the form URL from the Test URL to the Production URL and submit a live ticket.
- Check the Executions tab in n8n: notice the live execution appears without the test beaker icon, proving your workflow is now running in production!
Interactive Knowledge Checks
Debug a Missing Webhook Delivery
easyAn engineer deploys n8n with Docker Compose on a digital server. While the workflow runs fine during manual testing, incoming Telegram and GitHub webhooks consistently fail with Connection Refused or HTTP 404 errors. Identify the root cause and fix.
Predict Item-Level Execution Behavior
easySuppose an HTTP Request node returns an array of 25 customer JSON records. You connect this node directly to an Email Send node without an aggregation node. How many emails will n8n send?
Chapter Summary
- Deployment Architecture: Use n8n Cloud for instant prototyping; deploy self-hosted Docker Compose with PostgreSQL for unlimited executions, data privacy, and direct VPC access.
- Critical Security: Always configure and back up
N8N_ENCRYPTION_KEYto secure credentials, and setEXECUTIONS_DATA_PRUNE=trueto prevent disk exhaustion. - The Item Array Contract: All nodes exchange data as arrays of
{ json: { ... } }objects, with automatic per-item iteration. - Pin Data for Speed: Use the Pin Data feature during development to iterate rapidly on downstream nodes without re-triggering upstream events.
- Production Promotion: Test workflows using Test URLs and manual execution; activate the workflow to switch to persistent, production-grade endpoints.