A self-hosted n8n instance is a workflow editor on port 5678, a volume for its database, and a login you create on first open. This lab starts that container, builds Webhook to HTTP Request to If to Slack or email, proves it with curl, publishes it, then hangs an AI Agent and one tool off the same editor.
Run n8n with Docker
n8n’s Docker install page still prints a docker run, and it also says that page is outdated in favor of Docker Compose. The command below is the one that page still documents. As of October 2026 that page listed stable image behavior at 2.44.7 and beta at 2.45.4, and it said a new minor ships most weeks. Pin a tag from the n8n releases when you care which build you get. n8nio/n8n with no tag follows stable.
Create the volume first. Without it, a new container has an empty database and a new encryption key.
docker volume create n8n_data
Generate a key and pass it on the first start. n8n writes a random key into ~/.n8n if you skip this. You only get to choose the key before that file exists.
openssl rand -hex 32
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-e GENERIC_TIMEZONE="America/New_York" \
-e TZ="America/New_York" \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_ENCRYPTION_KEY="paste-the-hex-here" \
-v n8n_data:/home/node/.n8n \
n8nio/n8n
TZ is the container clock. GENERIC_TIMEZONE is what schedule-style nodes use. N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true is the setting the install page sets so the config file is not left world-readable. --rm deletes the container when it exits. The named volume stays. Open http://localhost:5678.
For a process that should survive the terminal, drop --rm and -it, and add -d, which is how the same docs restart a container after an image pull. Everyday docker run, docker ps, and volume commands are in the Docker CLI cheat sheet.
Default storage is SQLite inside that volume. Postgres is optional: set DB_TYPE=postgresdb plus the DB_POSTGRESDB_* variables from the install page. Keep the volume anyway. With Postgres the folder still holds the encryption key, logs, and source-control assets.
Update with a pull, then recreate the container against the same volume:
docker pull n8nio/n8n
docker stop n8n
docker rm n8n
Start again with the same -v, the same N8N_ENCRYPTION_KEY, and -d.
compose.yaml
This file uses the same image, port, volume path, and environment variables as the Docker install. It is not the sandbox stack from n8n’s full Compose guide. Compose file shape and docker compose up are in the Docker Compose cheat sheet.
Put the key in a .env file next to the compose file, and do not commit that file.
services:
n8n:
image: n8nio/n8n
restart: always
ports:
- "127.0.0.1:5678:5678"
environment:
GENERIC_TIMEZONE: America/New_York
TZ: America/New_York
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
docker compose up -d
docker compose ps
Binding 127.0.0.1 matches the cloud Compose example: the editor is local until a proxy publishes it. Update a Compose install with docker compose pull, docker compose down, then docker compose up -d. down does not remove a named volume unless you pass a flag that deletes volumes. Do not pass that flag.
Owner account, not basic auth
n8n 1.0 removed basic auth and external JWT. There is no supported switch that hides the login screen. On first open, the signup form creates the owner. The password must be at least eight characters and include a number and a capital letter.
From n8n 2.17.0 you can pre-provision that owner with environment variables. The switch is N8N_INSTANCE_OWNER_MANAGED_BY_ENV=true. The other owner fields live on the user-management environment page. Set them before you rely on the UI signup.
N8N_ENCRYPTION_KEY encrypts credentials before they hit the database. In queue mode every worker needs the same variable. If you change the key after credentials exist, those rows were encrypted with the old key. Keep a copy of the key outside the volume. A backup of workflows without this key cannot decrypt credentials. The CLI backup commands also omit the key. Copy the volume, or keep the env var, as well as n8n export:workflow.
Reverse proxy
n8n builds webhook URLs from N8N_PROTOCOL, N8N_HOST, and N8N_PORT. Behind a proxy the process still listens on 5678, while browsers use 443. Set the public origin yourself.
N8N_WEBHOOK_URL replaces WEBHOOK_URL. The old name is deprecated from n8n 2.35.0 and logs a warning. Set N8N_PROXY_HOPS=1 when there is one proxy. The last proxy on the path must send X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto.
export N8N_WEBHOOK_URL=https://n8n.example.com/
export N8N_PROXY_HOPS=1
The docs also say combining N8N_PATH with a reverse proxy breaks folder navigation. Prefer a subdomain such as n8n.example.com.
Build the first workflow
flowchart LR
W[Webhook] --> H[HTTP Request]
H --> I[If]
I -->|true| S[Slack]
I -->|false| E[Send Email]
Create a workflow. Add a Webhook node. Set HTTP Method to POST. Set Respond to When Last Node Finishes so curl prints the last node’s output instead of the generic “workflow got started” body. The other respond modes are Immediately, Using ‘Respond to Webhook’ Node, and Streaming response.
The node shows two URLs. Test is for the editor. Production is registered when you publish. Default path prefixes are webhook-test and webhook. You can change those with N8N_ENDPOINT_WEBHOOK_TEST and N8N_ENDPOINT_WEBHOOK. The Webhook docs cap the body at 16 MB. On your own server, N8N_PAYLOAD_SIZE_MAX raises that cap.
Add an HTTP Request node. It calls any REST API, and the node docs also say you can paste a curl command to fill the parameters. Point it at a health URL you control that returns JSON with a status field. Method GET is enough for the first test.
Add an If node. The docs spell the node If. Pick String, comparison is equal to, and compare the health JSON status to ok. Extra conditions combine with AND or OR. More than two outputs means a Switch node, not a stack of If nodes. String comparisons include contains, starts with, and matches regex. The false output is the branch you use when the check fails.
On the true output, add a Slack node. Resource Message, operation Send. That operation calls Slack’s chat.postMessage. You need a Slack credential. The Slack node page lists the method per operation and tells you to take scopes from the Slack credentials page. Do not guess scope strings.
On the false output, add a Send Email node. It sends through SMTP. Operation Send. Fill From Email, To Email, Subject, and Email Format: Text, HTML, or Both. The same node can be an AI tool later. It cannot set In-Reply-To or References. Threaded replies need the Gmail node’s reply operation, which the Send Email page names as the workaround.
Save the workflow.
Test with curl, then publish
Click Listen for test event on the Webhook node. The common-issues page says the test URL listens for 120 seconds and shows the payload in the editor. Copy the test URL from the node. It looks like this, with your path:
curl -X POST http://localhost:5678/webhook-test/health-check \
-H "Content-Type: application/json" \
-d "{\"service\":\"checkout\"}"
If nothing arrives, you waited too long or the workflow was not listening. Click Listen for test event again. A production URL returns nothing useful until you publish. Publishing is the current control. Older write-ups say “activate”. The executions docs say you publish a version, and you unpublish to stop production traffic.
After Publish, call the production URL. The editor does not show that run live. Open the Executions tab. Manual runs from the editor do not count toward a paid plan’s execution quota. A published webhook does: one execution per request that actually starts the workflow, including a body of {}. Malformed requests that fail before the workflow starts do not count. How that quota compares with Make credits and Zapier tasks is in n8n vs Make vs Zapier.
Add an AI Agent, a chat model, and a tool
The AI Agent node is a root node. You connect a chat model and at least one tool. The agent decides which tool to call. The docs require a tool. A model alone is not a valid setup.
From n8n 1.82.0 the old agent-type dropdown is deprecated. Every AI Agent node behaves as a Tools Agent. The v1 node that still shows the dropdown is scheduled to disappear in n8n 3.0. Update the node rather than picking a legacy type. For a database-style agent, the AI Agent page says to use a Postgres or MySQL tool on a current Agent node.
The Tools Agent page lists these chat models:
- OpenAI Chat Model
- Groq Chat Model
- Mistral Cloud Chat Model
- Anthropic Chat Model
- Microsoft Foundry Chat Model
Connect one of those to the model’s input, and create that provider’s credential. On the tool input, Calculator is enough to prove the loop, because it needs no account. HTTP Request and Slack are also tools on that list, so the same Slack account can be a tool instead of a fixed branch.
Set the prompt source to Define below and write the user message, or choose Take from previous node automatically, which expects an incoming field named chatInput. Options worth knowing: System Message, and Max Iterations, which defaults to 10. Enable Streaming is on by default and only streams when the trigger’s response mode is Streaming, for example a Webhook or the Chat Trigger.
A Chat Trigger plus a memory sub-node can hold a conversation across turns. The Tools Agent page says that memory does not persist between sessions.
What “tool calling” means outside n8n is covered in what is agentic AI. n8n’s AI Agent docs also point at connecting an MCP server as a tool. How those servers are shaped is in MCP servers explained.
Export and import
Workflows are JSON. In the editor, open the Workflow menu in the canvas header:
- Download saves the current workflow.
- Import from File reads a JSON file from your computer.
- Import from URL reads JSON from an address you can reach.
Copy and paste also works: select nodes, Ctrl + c, Ctrl + v.
The file contains credential names and IDs. IDs are not the secret. Names can still be sensitive if you named a credential after a customer. HTTP Request nodes filled from curl can store authentication headers in the JSON. Strip those before you send the file to someone else.
On the server, the backup commands write one file per workflow:
n8n export:workflow --backup --output=backups/workflows/
n8n export:credentials --backup --output=backups/credentials/
Inside Docker, that backups/ path is not the volume. Mount a host directory or copy the files out. Use a different directory for credentials. The backup page says import:credentials --separate fails if workflow files sit in the same folder. --decrypted writes credential secrets in plain text so you can move them to an instance with a different encryption key. Treat those files as secrets.
--published exports the published version instead of the draft. --version exports one historical version by its version id. You cannot combine --version with --all.
n8n also has packages (.n8np) via n8n-cli package. They are marked Preview. A package carries credential id, name, and type, never the secret. The import either matches a credential you already have or creates an empty placeholder.
Importing a workflow JSON does not import the SMTP password or the Slack token. Create those credentials again, then reselect them on the nodes.
Failures you will actually hit
404 on the webhook. You called the production path before Publish, or the test listener had already timed out at 120 seconds.
The URL in the node is localhost:5678 on a public site. Set N8N_WEBHOOK_URL and N8N_PROXY_HOPS. If an IP allowlist on the Webhook node rejects clients that should pass, the common-issues page tells you to set N8N_PROXY_HOPS to the number of proxies.
Credentials will not open after a recreate. The new container does not have the old key. Restore the volume or set the same N8N_ENCRYPTION_KEY you used on first launch.
Basic-auth environment variables do nothing. They were removed in 1.0. Use the owner account.
The AI Agent node will not run. Connect a tool. The hint on the AI Agent page is explicit.
Slack sends in the editor and never in production, or the reverse. That limitation is documented for the Slack Trigger, which can register only one webhook URL per Slack app. A Slack action node that posts a message does not have that constraint. The trigger case is fixed by unpublishing, pointing Slack at the test URL, then publishing again with the production URL.
FAQ
How do I run n8n in Docker so data survives a restart?
Create a named volume and mount it at /home/node/.n8n. That directory holds the SQLite database and the credential encryption key. Set N8N_ENCRYPTION_KEY before the first start if you want a key you control, and keep the volume when you replace the container.
Why does my n8n webhook return 404?
The test URL only listens for about 120 seconds after you click Listen for test event. The production URL is registered when you publish the workflow. Behind a reverse proxy, set N8N_WEBHOOK_URL to the public https origin and N8N_PROXY_HOPS to 1, or the editor shows an internal URL.
Does N8N_BASIC_AUTH still protect a self-hosted n8n?
No. Basic auth and external JWT login were removed in n8n 1.0. Current versions always show a login screen. The first account you create is the owner. The password needs at least eight characters, one number, and one capital letter.
Do exported n8n workflow JSON files include API keys?
They include credential names and IDs, not the secret values. You recreate credentials on the destination instance. An HTTP Request node imported from a curl command can still contain an Authorization header in the node JSON, so read the file before you share it.
How this guide was verified
Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .
Official sources read for this article
- docs.n8n.io/deploy/host-n8n/install-options/install-with-docker
- docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/configuration-examples/set-a-custom-encryption-key
- docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/configuration-examples/configure-webhook-urls-with-reverse-proxy
- docs.n8n.io/deploy/host-n8n/configure-n8n/user-management
- docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/user-management-and-2fa
- docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/endpoints
- docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook
- docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/common-issues
- docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest
- docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if
- docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack
- docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.sendemail
- docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent
- docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent
- docs.n8n.io/build/manage-workflows/export-and-import
- docs.n8n.io/deploy/host-n8n/configure-n8n/use-the-command-line
- docs.n8n.io/deploy/host-n8n/keep-n8n-running/backup-and-restore
- docs.n8n.io/changelog/v10-migration-guide

