Self-Host n8n with Docker
Launch a local n8n automation engine and build a live webhook workflow.
Introduction
30 Second Summary
Every time you use a cloud automation tool, you are renting someone else's infrastructure. They set the pricing, the usage limits, and the rules.
In this project, you will run n8n, an open-source automation platform, inside a Docker container on your own computer. You will build a webhook workflow that receives JSON data, transforms it, and returns a formatted response to your terminal.
What You'll Build
Picture posting data from your terminal and watching your self-hosted automation engine transform it and fire back a custom JSON response.
By the end of this project, you'll have:
- A self-hosted n8n instance running in Docker that you can open in your browser and use to design automation workflows, with your data persisting across restarts.
- A webhook endpoint you can hit from your terminal to send JSON data, watch it get transformed by n8n expressions, and receive a formatted response back.
- A published production workflow that responds to HTTP requests on its own, without you clicking anything in the editor.
- Secret Mission: Add conditional routing with an IF node that returns different responses based on the incoming data.
Are there any prerequisites?
This project includes setup instructions for everything you need. You will use Docker to run n8n, and the first step walks you through verifying your installation.
Before We Start
You are about to spin up your own automation engine and ship your first live workflow on it. Before any hands-on work, take a moment to commit to what you are building and why it matters to you.
Set Up Docker and Launch n8n
Before you can build any automation, you need an engine to run it. n8n is an open-source workflow automation tool that gives you a visual canvas for connecting services, transforming data, and triggering actions.
Docker makes spinning up n8n simple. You pull an image, run a container, and your automation engine is live.
In this step, get ready to:
- Verify that Docker is installed and running.
- Launch n8n in a Docker container with persistent storage.
- Create your n8n owner account and open the editor canvas.
Verify Docker is installed
- Confirm Docker is ready on your machine by running this command:
docker --version
You should see output starting with Docker version followed by a version number.
Don't see a version number?
If you see command not found, Docker is not installed yet. Follow the official Docker Engine installation guide for Linux to install it for your distribution.
After installation, verify Docker is working by running docker run hello-world. You should see a "Hello from Docker" message.
Still stuck? Help me troubleshoot my Docker installation on Linux.
Pull and run n8n in a Docker container
Data inside a container disappears when the container is removed. You need a volume to keep your workflows and settings safe across restarts.
- Create a persistent storage volume for n8n by running this command:
docker volume create n8n_data
You should see n8n_data printed, confirming the volume was created.
What is a Docker volume?
A Docker volume is a storage area that lives outside the container. Even if you stop or delete the n8n container later, your workflows, credentials, and settings remain intact in the volume.
- Start the n8n container by running this command. Replace the timezone placeholder with your timezone from the tz database, such as America/New_York, Europe/London, or Asia/Tokyo:
docker run -d \
--name n8n \
-p 5678:5678 \
-e GENERIC_TIMEZONE=[[YOUR_TIMEZONE="your timezone, e.g. America/New_York"]] \
-e TZ=[[YOUR_TIMEZONE="your timezone, e.g. America/New_York"]] \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_RUNNERS_ENABLED=true \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n
What does this command do?
- -d runs the container in the background so you can keep using your terminal.
- --name n8n gives the container a friendly name you can reference in other commands.
- -p 5678:5678 maps port 5678 inside the container to port 5678 on your machine.
- The -e flags set your timezone and enable required n8n settings.
- -v n8n_data:/home/node/.n8n connects the volume you just created to n8n's data directory.
- docker.n8n.io/n8nio/n8n is the official n8n image. Docker pulls it automatically the first time.
Docker prints a long string of characters after running the command. That is the container ID, confirming it started.
- Verify the container is running by running this command:
docker ps
You should see a container named n8n with port 5678 mapped and a status of Up.
Container not showing up?
If docker ps shows nothing, the container may have exited immediately. Run docker logs n8n to see any error messages.
A common cause is port 5678 already being used by another process. Check with lsof -i :5678 and stop the conflicting process before trying again.
If you see a permissions error, you may need to run the command with sudo or add your user to the docker group.
Help me debug why my n8n container won't start.
Create your owner account
Your n8n container is running. Before you open the browser, what do you think you will see at http://localhost:5678?
- Open http://localhost:5678 in your web browser.
The n8n setup screen loads, asking you to create your first owner account. This is the admin account that controls your entire n8n instance.
- Enter your name in the name field.
- Enter your email address.
- Choose a password and confirm it.
- Submit the form to create your account.
Once submitted, you land on the n8n editor canvas. This is your visual workspace for building automations.
What is the n8n editor canvas?
The canvas is where you build workflows by dragging, connecting, and configuring nodes. Each node performs one action, like receiving a webhook, transforming data, or sending a message.
Every workflow you create lives on its own canvas. You will build your first one in the next step.
Browser shows a connection error?
n8n can take 10-20 seconds to fully start after the container launches. Wait a moment and refresh the page.
If the page still does not load, switch back to your terminal and run docker ps to confirm the container is still running. If it has stopped, check docker logs n8n for errors.
The n8n setup page won't load in my browser. Help me fix this.
Your automation engine is live. Next up, you will build your first webhook and send data to it from the terminal.
Build Your First Webhook
Your automation engine is running. Now it needs a way for the outside world to talk to it.
A webhook gives your workflow its own URL. When a service sends data to that URL, n8n kicks off the workflow automatically. In this step, you'll set up a webhook and test it with real JSON data from your terminal.
In this step, get ready to:
- Create a Webhook trigger node on the n8n canvas.
- Test the webhook by sending a JSON payload with curl.
Create a new workflow with a Webhook trigger
The n8n editor canvas is where you visually build workflows by connecting nodes together. Your first node will be a Webhook trigger that listens for incoming HTTP requests.
- In the n8n editor, click Add first step on the canvas.
- In the nodes panel that opens, type Webhook into the search bar.
- Select the Webhook node from the results.
The Webhook node appears on the canvas and its settings panel opens on the right.
What is a webhook?
A webhook is a URL that waits for incoming HTTP requests. When a request hits that URL, it triggers whatever workflow is attached to it.
In n8n, the Webhook node generates this URL for you and passes the incoming data to the rest of your workflow. It is the most common way to let external services start an automation.
- In the Webhook node settings panel, find the HTTP Method dropdown. It defaults to GET. Change it to POST.
- Find the Path field below the method dropdown. Replace the auto-generated value with hello-automation.
Look at the top of the Webhook node panel. A Test URL is displayed. It looks like http://localhost:5678/webhook-test/hello-automation. You will use this URL in the next substep to send test data.
Why a Test URL?
n8n gives each Webhook node two URLs: a Test URL and a Production URL. The Test URL only works while the editor is actively listening, so you can inspect the incoming data right in the editor.
You will use the Production URL later in Step 4 when you publish your workflow for real traffic.
Test the webhook with curl
Your webhook has a URL, but it has never received a request. Time to send one and see what happens.
- In the n8n editor, click Listen for Test Event in the Webhook node panel.
120-second window
The test listener stays active for 120 seconds. If it times out before you send your request, click Listen for Test Event again to restart the timer.
- Open a second terminal window. You need a separate terminal so you can run curl while keeping the n8n editor visible in your browser.
Before you send this request, take a guess. What do you think n8n will send back as the curl response?
- Send a test request to your webhook by running this command in your second terminal:
curl --request POST http://localhost:5678/webhook-test/hello-automation \
--header 'Content-Type: application/json' \
--data '{"name": "Alice", "email": "alice@example.com", "message": "Hello from the terminal"}'
What does this command do?
- --request POST sends an HTTP POST request to your webhook's Test URL.
- --header 'Content-Type: application/json' tells the server you are sending JSON data.
- --data attaches a JSON object with three fields: name, email, and message.
Switch to the n8n editor in your browser. The Webhook node now shows the incoming data in its output panel. You can see the body, headers, params, and query from the request.
Now look at your terminal. The curl response is just this:
{"message":"Workflow got started"}
The webhook received your data. You can see it right there in the editor. But the response it sent back is a generic acknowledgment with none of the actual data.
Why did this happen?
By default, the Webhook node's Respond setting is set to Immediately. This means n8n fires back a response the instant the request arrives, before any downstream nodes have processed the data.
That is why you only see {"message":"Workflow got started"}. The webhook confirmed it received the request, but it never waited to send anything useful back.
Not seeing data in the n8n editor?
- The test listener times out after 120 seconds. Click Listen for Test Event again in the Webhook node panel and rerun the curl command.
- Make sure your n8n container is still running. Check with docker ps in your terminal.
- Double-check the URL in your curl command matches the Test URL shown in the Webhook node panel.
Why isn't my webhook receiving data from curl?
Your webhook receives data perfectly. The gap is in what it sends back. In the next step, you will transform the incoming data and configure the webhook to return it.
Transform Data and Fix the Response
Your webhook is receiving data and showing it in the editor. That is progress. But the response your terminal got back was just {"message":"Workflow got started"}.
That generic acknowledgment is n8n's default behavior. It responds immediately before any processing happens. To turn your webhook into something that works like an API endpoint, you need to add a transformation step and tell n8n to wait until it finishes before responding.
In this step, get ready to:
- Add an Edit Fields node that reshapes the incoming JSON data into a clean response.
- Configure the Webhook node to respond after processing finishes instead of immediately.
- Test the full pipeline with curl and see the transformed response come back.
Add an Edit Fields node to transform the data
Right now your workflow has one node. The Webhook node receives data, but nothing processes it. You need a second node that takes the raw payload and reshapes it into the fields you want to send back.
- In the n8n editor, click the + connector on the right side of the Webhook node.
- In the nodes panel that opens, search for Edit Fields.
- Select the Edit Fields (Set) node to add it to the canvas.
The Edit Fields node opens with an empty field list. You will now add three output fields.
- Click Add Field and select String.
- In the Name field, type greeting.
- For the Value field, hover over it and click the Expression tab to switch from fixed to expression mode.
- Enter the following expression:
{{ "Hello, " + $json.body.name + "!" }}
- Click Add Field again and select String.
- Set the Name to original_message.
- Switch the Value to Expression mode and enter:
{{ $json.body.message }}
- Click Add Field one more time and select String.
- Set the Name to status.
- Leave the Value in Fixed mode (the default) and type processed.
What do these expressions do?
The {{ }} syntax is n8n's expression language. It lets you pull values from earlier nodes and transform them inline.
- $json.body.name reaches into the Webhook node's output and grabs the name field from the POST body. The + operator concatenates it into a greeting string.
- $json.body.message passes the original message through unchanged.
- The status field uses a fixed string (processed) because it is the same for every request.
One last toggle to set. By default, the Edit Fields node passes all original data through alongside your new fields. You want to return only the three fields you defined.
- In the Edit Fields node panel, find the Keep Only Set toggle at the bottom of the fields list.
- Switch it to on (green).
Why turn on Keep Only Set?
Without this toggle, the response would include the raw webhook data (headers, params, query) alongside your three clean fields. Turning it on strips everything except greeting, original_message, and status.
Configure the Webhook to respond with transformed data
The Edit Fields node is ready, but the Webhook node is still set to respond immediately. You need to change it so n8n waits until the Edit Fields node finishes before sending the response.
- Click the Webhook node on the canvas to open its configuration panel.
- Find the Respond setting (currently set to Immediately).
- Change it to When Last Node Finishes.
- A new Response Data dropdown appears. Set it to First Entry JSON.
What changed?
Previously, the Webhook node fired back {"message":"Workflow got started"} the instant a request arrived. Now it waits for the Edit Fields node to finish processing, then returns that node's output as the HTTP response.
First Entry JSON tells n8n to return the first item's data as a JSON object instead of wrapping it in an array.
Test the full pipeline
Both nodes are configured. Time to send the same request from Step 2 and see whether the response has changed.
- Click the Webhook node on the canvas to open it.
- Click Listen for test event to start listening.
Before you run the next command, think about what you expect to see this time. Will the terminal still show Workflow got started, or something different?
- Switch to your second terminal and run this curl command:
curl --request POST http://localhost:5678/webhook-test/hello-automation \
--header 'Content-Type: application/json' \
--data '{"name": "Alice", "email": "alice@example.com", "message": "Hello from the terminal"}'
This time, curl returns your transformed JSON:
{
"greeting": "Hello, Alice!",
"original_message": "Hello from the terminal",
"status": "processed"
}
Your workflow now receives data, transforms it, and returns the result. It works like an API endpoint.
Still seeing Workflow got started?
- Make sure you changed the Respond setting on the Webhook node to When Last Node Finishes. If it is still set to Immediately, n8n responds before the Edit Fields node runs.
- Confirm you clicked Listen for test event before sending the curl request. The test webhook only listens for 120 seconds.
- Check that the Edit Fields node is connected to the Webhook node. Click the line between them to confirm the connection is solid.
Why is my webhook still returning the default response?
Your workflow transforms and returns data on demand. Next up, you will publish the workflow so it responds to requests around the clock without you clicking anything in the editor.
Publish and Go Live
Your workflow now transforms incoming JSON and returns a personalized response. That is a complete automation pipeline.
There is one limitation left. You have been using the Test URL, which expires after 120 seconds and requires you to click a button each time. In this step, you will publish the workflow so it responds on a Production URL autonomously.
In this step, get ready to:
- Name and publish the workflow.
- Test the Production URL with curl.
- Verify the production execution is logged in the Executions tab.
Name, save, and publish the workflow
A published workflow registers its Production URL permanently. n8n listens for incoming requests around the clock without you touching the editor.
- Click the workflow name field at the top of the n8n editor canvas.
- Type Hello Automation to replace the default name.
- Click Publish in the top-right corner of the editor.
- A publishing modal appears. Click Publish again to confirm.
The workflow name at the top of the canvas now reads Hello Automation. The Publish button is now disabled because there are no unpublished changes.
What does publishing do?
Publishing makes your workflow live. n8n registers the Production URL for the Webhook node and begins listening for incoming requests automatically.
Every time you make changes after this, n8n autosaves them as a draft. Those changes only go live when you click Publish again.
Publish button is disabled?
Make sure all nodes on your canvas are connected. A workflow with disconnected nodes cannot be published. If you accidentally detached the Edit Fields node, drag a connection from the Webhook node's output to the Edit Fields node's input.
Help me publish my n8n workflow
Test the production endpoint
Your published workflow has a Production URL that stays active permanently. You do not need to click anything in the editor for it to respond.
- Click the Webhook node on the canvas to open its configuration panel.
- At the top of the panel, toggle from Test URL to Production URL.
The Production URL looks like http://localhost:5678/webhook/hello-automation.
What changed in the URL?
The Test URL uses the path /webhook-test/. The Production URL uses /webhook/.
Test URLs require you to click Listen for test event and expire after 120 seconds. Production URLs are always active as long as the workflow is published.
Before you run the next command, predict what will happen. Will you need to click anything in the n8n editor first? Will the response look different from the test?
- Send a request to the Production URL by running this command in your terminal:
curl --request POST http://localhost:5678/webhook/hello-automation \
--header 'Content-Type: application/json' \
--data '{"name": "Bob", "email": "bob@example.com", "message": "Testing production"}'
The response appears instantly. You did not click anything in the n8n editor.
What should I see?
Your terminal shows the same transformed JSON structure you saw in the previous step, now with Bob's data:
- greeting shows Hello, Bob!.
- original_message shows Testing production.
- status shows processed.
The workflow ran autonomously. n8n processed the request the moment it arrived, without any manual trigger.
Now confirm that n8n logged the production execution.
- In the n8n editor, select the Executions tab in the top menu of your workflow.
You see the production execution listed with its timestamp, a Success status, and the data that flowed through each node. This is where every future production run gets logged.
Why doesn't the editor canvas show execution data?
Production executions do not display their data flow on the canvas the way test executions do. The canvas only shows data during manual or test runs.
To inspect what happened during a production run, use the Executions tab. Click any execution in the list to see the full data at each node.
Getting a 404 or no response?
- Check that your URL uses /webhook/ not /webhook-test/. The test path only works during the 120-second listening window.
- Make sure the workflow is published. If the Publish button is active, your latest changes have not been published yet.
- Verify the n8n container is still running by running docker ps in your terminal. If the container is not listed, restart it with docker start n8n.
Help me troubleshoot my n8n production webhook
Secret mission
Add Conditional Routing
Your workflow handles every request the same way right now. What if some messages are urgent and need different treatment? Add an IF node to route requests down different paths based on the incoming data.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- A Docker container named n8n running on your machine (uses local CPU and RAM, no cloud costs).
- A Docker volume named n8n_data storing your workflows and credentials.
Keep everything running
No action needed. Choose this if you are still actively building workflows or plan to continue shortly.
- Your n8n container stays running in the background.
- The n8n editor remains available at http://localhost:5678.
- Your published workflows keep responding to requests on their production URLs.
Pause - I'll come back to this later
Stop the running container to free up CPU and RAM. Your data persists in the volume, so nothing is lost.
- Stop the container by running:
docker stop n8n
Your workflows, credentials, and owner account are all stored in the n8n_data volume. They will be exactly as you left them.
- When you are ready to come back, restart the container by running:
docker start n8n
The n8n editor will be available again at http://localhost:5678 within a few seconds.
Delete - I don't want to use this again
Remove the container, volume, and all saved workflows permanently. This cannot be undone.
- Stop and remove the n8n container, then delete the n8n_data volume by running:
docker stop n8n && docker rm n8n && docker volume rm n8n_data
What does this command do?
- docker stop n8n gracefully stops the running container.
- docker rm n8n removes the stopped container.
- docker volume rm n8n_data deletes the data volume, which includes all your workflows, credentials, and your owner account.
- Confirm the container is gone by running:
docker ps -a --filter name=n8n
You should see an empty table with no containers listed.
- Confirm the volume is gone by running:
docker volume ls --filter name=n8n_data
You should see no volumes listed. Everything has been removed from your machine.
Nice Work!
Nice Work!
You just built and shipped your own automation engine from scratch. You have a self-hosted n8n instance running in Docker, a live webhook workflow that processes real HTTP requests, and a production endpoint that responds on its own without you touching the editor.
You learned how to:
- Launch a self-hosted n8n instance inside a Docker container with a persistent volume so your workflows survive restarts.
- Build a webhook-triggered workflow that receives JSON data, transforms it with n8n expressions, and returns processed results back to the caller.
- Publish a workflow to the Production URL so it responds to requests autonomously without clicking anything in the editor.
- Secret Mission: Add conditional routing with an IF node to return different responses based on incoming request data.
Ready to quiz yourself?