Listen to this post

n8n recently launched an AI Assistant, their equivalent of Claude Code. I was excited to try it out, especially given my backlog of workflows for CodeCompanion.nvim, but setting it up on a self-hosted box looked like Docker and Linux hell. It wasn’t quite that bad, but this post walks through all the steps I took to get it working.

Migrating n8n

Important

This post was written by hand. Every CLI command is mine. I only used an LLM to proofread the text and correct grammar and spelling.

Context

n8n’s AI Assistant launch looked incredibly promising from the intro video and documentation. From initial testing, it looks like it’ll save me a ton of development time when building workflows, especially useful given the list of automations I want to build for CodeCompanion.nvim.

Reading the docs, though, I noticed the feature requires a sandbox, and n8n’s docs push Daytona. I’d never heard of Daytona, and I’ve no interest in signing up for yet another subscription. Especially when my entire n8n box costs me less than $10 a month (even with my LLM API credits).

Thankfully, n8n also supports a self-hosted sandbox service in Docker. A quick read of those instructions told me there’d be plenty of back and forth to get it working. Nothing like running a command in Ubuntu and turning to Google to resolve the issue*.

Before we dive in, a word on my n8n setup. I self-host n8n on a Hetzner box, using Docker and Caddy. You can find out how to do this yourself in the n8n docs.

Note

* Why no LLM? Well, their knowledge cut-off date can be a constraint for new Linux packages and GitHub repos.

Who Is This Guide For?

If you self-host your n8n instance, use Ubuntu 24 or below (I’ll get to why) and have an API key for an LLM of your choice, follow along.

Steps

Note

I’m not setting up the AI Assistant to have any web search capabilities.

1. Install sysbox

For this post, I’ll be using n8n’s official sandbox service, which leverages sysbox. In summary, this allows you to run workloads in Docker like a virtual machine. I’ll lean on n8n’s Quickstart: Linux page for a large chunk of this.

So, log into your server and download the sysbox setup script with:

curl -fsSL -o setup-sysbox.sh https://raw.githubusercontent.com/n8n-io/n8n-sandbox-service/refs/heads/main/scripts/setup-sysbox.sh
chmod +x setup-sysbox.sh

Then you can check you have the necessary pre-requisities with:

./setup-sysbox.sh --dry-run

At this point I got a ton of errors stating that sysbox only works with Ubuntu 24 or below. My Hetzner box had been provisioned with Ubuntu 26, so I had to follow my previous blog post and migrate down to Ubuntu 24. Truly painful.

Once that was done, I ran:

./setup-sysbox.sh

Verifying it with:

docker info --format '{{json .Runtimes}}' | jq '.["sysbox-runc"]'

2. Setting up the Sandbox

At this point the quickstart guide tells you to download the production compose.yaml and .env.example files. I noticed a couple of bugs in those files, which I’ve fixed via a PR to the official repo. To cut a long story short, I used a hybrid of those files and the Docker Compose example from the AI Assistant docs page.

First, create a new directory to house the sandbox docker-compose files:

mkdir n8n-sandbox and cd n8n-sandbox

I like to keep my server neat and tidy, separating different components, physically, where possible.

Then open up Vim with:

vim docker-compose.yml

Important

I’m a Vim person, so all my edits use it. Swap in nano if that’s more to your liking.

and paste in this Docker Compose file:

volumes:
  sandbox-tls:

networks:
  n8n-shared:
    external: true

services:
  # One-shot: generates mTLS certificates, then exits
  sandbox-certs:
    image: n8nio/n8n-sandbox-service-api:latest
    user: '0:0'
    entrypoint: ['sh', '-c']
    command:
      - >
        bootstrap-mtls.sh --out-dir /tls --api-san sandbox-api
        --control-san-prefix sandbox-runner --world-readable &&
        chown -R sandbox-api:sandbox-api /tls/api && chmod -R a+rX /tls
    volumes:
      - sandbox-tls:/tls
    environment:
      NUM_RUNNERS: '1'

  sandbox-api:
    image: n8nio/n8n-sandbox-service-api:latest
    depends_on:
      sandbox-certs:
        condition: service_completed_successfully
    ports:
      - "8080:8080"
    volumes:
      - sandbox-tls:/tls:ro
    environment:
      SANDBOX_API_KEYS: your-sandbox-api-key
      SANDBOX_API_RUNNER_REGISTRATION_TOKEN: your-registration-token
      SANDBOX_API_RUNNER_API_KEY: your-runner-key
      SANDBOX_API_GRPC_TLS_CERT_FILE: /tls/api/grpc-server.crt
      SANDBOX_API_GRPC_TLS_KEY_FILE: /tls/api/grpc-server.key
      SANDBOX_API_GRPC_TLS_CLIENT_CA_FILE: /tls/api/ca.crt
      SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_CA_FILE: /tls/api/ca.crt
      SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_CERT_FILE: /tls/api/control-grpc-api-client.crt
      SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_KEY_FILE: /tls/api/control-grpc-api-client.key
      SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_SERVER_NAME: sandbox-runner-1
    networks:
      - default
      - n8n-shared

  sandbox-runner-1:
    image: n8nio/n8n-sandbox-service-runner-dind:latest
    privileged: true
    volumes:
      - sandbox-tls:/tls:ro
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:8080/readyz"]
      interval: 5s
      timeout: 5s
      retries: 24
      start_period: 20s
    environment:
      SANDBOX_RUNNER_API_KEYS: your-runner-key
      SANDBOX_RUNNER_REGISTRATION_TOKEN: your-registration-token
      SANDBOX_RUNNER_API_GRPC_ADDR: sandbox-api:9090
      SANDBOX_RUNNER_HTTP_BASE_URL: http://sandbox-runner-1:8080
      SANDBOX_RUNNER_CONTROL_GRPC_LISTEN_ADDR: ':9091'
      SANDBOX_RUNNER_CONTROL_GRPC_ADVERTISE_ADDR: sandbox-runner-1:9091
      SANDBOX_RUNNER_ID: runner-1
      SANDBOX_RUNNER_DOCKER_SANDBOX_IMAGE: n8nio/n8n-sandbox-service-sandbox:latest
      SANDBOX_RUNNER_REGISTRATION_GRPC_CA_FILE: /tls/runner/ca.crt
      SANDBOX_RUNNER_REGISTRATION_GRPC_CERT_FILE: /tls/runner/grpc-client.crt
      SANDBOX_RUNNER_REGISTRATION_GRPC_KEY_FILE: /tls/runner/grpc-client.key
      SANDBOX_RUNNER_REGISTRATION_GRPC_SERVER_NAME: sandbox-api
      SANDBOX_RUNNER_CONTROL_GRPC_TLS_CERT_FILE: /tls/runner/control-grpc-server.crt
      SANDBOX_RUNNER_CONTROL_GRPC_TLS_KEY_FILE: /tls/runner/control-grpc-server.key
      SANDBOX_RUNNER_CONTROL_GRPC_TLS_CLIENT_CA_FILE: /tls/runner/ca.crt

There are three variables you’ll need to replace:

  1. your-sandbox-api-key
  2. your-registration-token
  3. your-runner-key

I used 1Password’s random password generator to create some secure keys and pasted them in.

As I mentioned above, this file came from a mix of the official docs and the example file over on the n8n-sandbox-service repo. A couple of things caught me out along the way.

First, the docker-compose file from the GitHub repo pulls in images from n8nio/n8n-sandbox-* when they should be n8nio/n8n-sandbox-service-*. This led to this joyous error:

Error response from daemon: pull access denied for n8nio/n8n-sandbox-api, repository does not exist or may require 'docker login'

Second, the Docker Compose file must also add an external network so it can communicate with the existing n8n-docker-caddy service. Without it, every conversation with the AI Assistant errors out. Make sure you create the network before moving on:

docker network create n8n-shared

3. Running the Sandbox

At this point, if you run:

docker compose up -d

You may see the following error:

Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: namespace {"time" ""} does not exist

Thankfully this error led me to this sysbox GitHub issue which proposed editing the /etc/docker/daemon.json file. So, doing:

vim /etc/docker/daemon.json

and adding the below:

{
  "features": {
    "time-namespaces": false
  }
}

Now if you try:

docker compose up -d

You should see the following output:

 ✔ Network n8n-sandbox_default              Created
 ✔ Container n8n-sandbox-sandbox-runner-1-1 Started
 ✔ Container n8n-sandbox-sandbox-certs-1     Exited
 ✔ Container n8n-sandbox-sandbox-api-1      Started

I also recommend doing:

curl http://localhost:8080/healthz

Verifying that {"status":"ok"} is returned.

Finally. The sandbox lives!


As an aside, my full daemon.json file looks like:

{
  "runtimes": {
    "sysbox-runc": {
      "path": "/usr/bin/sysbox-runc"
    }
  },
  "features": {
    "time-namespaces": false
  }
}

During testing, I had somehow overwritten the runtimes key and received the following error:

Error response from daemon: unknown or invalid runtime name: sysbox-runc

4. Updating n8n-docker-caddy for the Sandbox

One docker-compose.yml file down, one more to go.

Navigate to the n8n-docker-caddy folder with:

cd ~/n8n-docker-caddy

Open the file with:

vim docker-compose.yml

Now, depending on what your docker-compose.yml file looks like, you’ll need to give the services knowledge of the external network we’ve just created.

For the n8n service, I added:

networks:
  - default
  - n8n-shared

And at the bottom of my docker-compose.yml file I added:

networks:
  n8n-shared:
    external: true

Next, I added the environment variables for the sandbox and for the model provider, as per the n8n documentation. Enabling the AI Assistant with N8N_ENABLED_MODULES=instance-ai:

- N8N_ENABLED_MODULES=instance-ai
- N8N_INSTANCE_AI_MODEL=openrouter/~anthropic/claude-sonnet-latest
- N8N_INSTANCE_AI_MODEL_API_KEY=my-openrouter-api-key
- N8N_INSTANCE_AI_SANDBOX_ENABLED=true
- N8N_INSTANCE_AI_SANDBOX_PROVIDER=n8n-sandbox
- N8N_SANDBOX_SERVICE_URL=http://sandbox-api:8080
- N8N_SANDBOX_SERVICE_API_KEY=your-sandbox-api-key

I’ve chosen to use OpenRouter and the latest version of Claude Sonnet. In testing, I found Opus to be overkill, going directly against what the n8n docs recommend. I like OpenRouter because I can create an API key specifically for this feature, track its usage as well as putting a spend limit on it.

Note

Be sure that your-sandbox-api-key matches the one used in the sandbox’s docker-compose.yml file


After all this, my docker-compose.yml file ended up looking like:

services:
  caddy:
    image: 'caddy:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - 'caddy_data:/data'
      - '${DATA_FOLDER}/caddy_config:/config'
      - '${DATA_FOLDER}/caddy_config/Caddyfile:/etc/caddy/Caddyfile'

  n8n:
    build:
      context: .
      dockerfile: Dockerfile
    image: docker.n8n.io/n8nio/n8n
    restart: always
    ports:
      - '5678:5678'
      - '5679:5679'  # Task broker port
    environment:
      - 'N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}'
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_ENCRYPTION_KEY=some-secret-key
      - NODE_ENV=production
      - 'WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/'
      - 'GENERIC_TIMEZONE=${GENERIC_TIMEZONE}'
      - N8N_EMAIL_MODE=smtp
      - N8N_SMTP_HOST=some-host
      - N8N_SMTP_PORT=587
      - N8N_SMTP_USER=some-email
      - N8N_SMTP_PASS=some-password
      - N8N_SMTP_SENDER=some-email
      - N8N_SMTP_SSL=false
      # Task runner configuration
      - N8N_RUNNERS_ENABLED=true
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
      - N8N_RUNNERS_AUTH_TOKEN=some-auth-token
      - N8N_NATIVE_PYTHON_RUNNER=true
      # n8n sandbox
      - N8N_ENABLED_MODULES=instance-ai
      - N8N_INSTANCE_AI_MODEL=openrouter/~anthropic/claude-sonnet-latest
      - N8N_INSTANCE_AI_MODEL_API_KEY=my-openrouter-api-key
      - N8N_INSTANCE_AI_SANDBOX_ENABLED=true
      - N8N_INSTANCE_AI_SANDBOX_PROVIDER=n8n-sandbox
      - N8N_SANDBOX_SERVICE_URL=http://sandbox-api:8080
      - N8N_SANDBOX_SERVICE_API_KEY=your-sandbox-api-key
    volumes:
      - 'n8n_data:/home/node/.n8n'
      - '${DATA_FOLDER}/local_files:/files'
    networks:
      - default
      - n8n-shared

  n8n-task-runner:
    image: n8nio/runners:latest
    restart: always
    depends_on:
      - n8n
    environment:
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
      - N8N_RUNNERS_AUTH_TOKEN=some-auth-token
      - N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT=60

networks:
  n8n-shared:
    external: true

volumes:
  caddy_data:
    external: true
  n8n_data:
    external: true

Important

Chances are, yours looks very different. However, that’s not the point of this blog post.

5. Restarting n8n-docker-caddy

To account for the updated docker-compose.yml file, execute:

docker compose down
docker compose up -d

6. Testing

After this, I navigated to https://my-n8n-instance.com and clicked AI Assistant:

n8n AI Assistant

Before testing it out with a simple prompt:

Can you create a "Hello, World" workflow for me?
n8n AI Assistant

And boy, did it!

Summary

Getting n8n’s AI Assistant running on a self-hosted server took more fiddling than I’d hoped: downgrading Ubuntu (a “me” problem), hunting down a sysbox namespace bug, fixing wrong Docker image names, and wiring up an external Docker network. None of it was hard, but it was cumbersome, as is nearly every experience I ever have with Linux.

If you’re self-hosting n8n and want the AI Assistant without paying for Daytona, follow the steps above and you’ll have it running in under an hour. Just make sure you’re on Ubuntu 24 or another OS that sysbox supports.

Enjoy!

Update 2026-07-15

After some proper testing, I’m impressed with the AI Assistant’s output, though it comes at a cost.

My use case

GitHub makes it hard to see historical insights on your repos: traffic, clones, stars, etc. I wanted a workflow that collects these weekly and stores them in a datatable, the kind of analytics that lets me spot trends and keep my ego pumped.

The build

I threw this at the AI Assistant, enabled with claude-sonnet-5, with the following prompt:

Can you create a workflow that will read my GitHub repository (https://github.com/olimorris/codecompanion.nvim) and fetch weekly analytics like clones, pulls, visitors etc and put them in a data table so I an can use them at a later date?

At the same time, I tracked token consumption in OpenRouter.

The AI Assistant did a smashing job. It created the datatable and the workflow perfectly, in one shot, and even included stats like stargazers and issues that I hadn’t asked for:

Custom workflow

The best part: I could publish this workflow right away.

In the image, you can see I flagged its lack of web search (I haven’t enabled that). The Assistant ploughed on regardless.

Observations

My hunch is that the AI Assistant leans on n8n’s own skills to “know” how to build workflows. Beyond that, it has tools giving it greater visibility into the user’s n8n instance:

Custom workflow

I saw no evidence of compaction or context editing that would trim the request payload down. But for this workflow, I doubt we’re even close to any context limits.

The cost

Here’s where it got interesting: this build cost me $3.52.

Custom workflow

For about 5-10 minutes of work. How long would it have taken me to build by hand? I estimate an hour, maybe two, once I’d researched the GitHub APIs and done the usual troubleshooting.

From a productivity perspective, that’s a huge win, and it works out significantly cheaper than my daily rate. However, for large and complex workflows, expect the incremental cost of new features/modifications with an already heavy context, to be significant.

Closing thoughts

If I was a small-medium sized business, the AI Assistant would stop me going to Upwork, Fiverr and the like. I’d also be keen to see what the AI Assistant can do with a model that is a step below claude-sonnet-5. Can we get the same output at reduced cost?