keepcalculator

06 Numbers and Powers

Run n8n & Node.js API in One Docker Compose Stack

Card No. 06·67488 · Updated · Standard formula, shown below

Yes, you can run n8n and a Node.js API together in a single local Docker Compose stack, and it is the standard way to develop and test automation workflows that need custom endpoints. You define both services in one docker-compose.yml, link them with a shared network, and use environment variables to let n8n call your API by its service name.

Why Run Them Together

n8n is a workflow automation tool that can call HTTP endpoints, but it cannot run arbitrary JavaScript logic beyond its built-in Code node. A separate Node.js API fills that gap: you can write custom validation, database queries, file processing, or integrations with third-party SDKs that n8n’s visual nodes cannot express. Running both in the same Compose stack means they share a Docker network, so n8n can reach your API at a stable internal hostname (http://api:3000) instead of a hard-coded localhost that breaks when containers are recreated.

This setup is common for local development because it mirrors production, where n8n and your API would run as separate containers in the same orchestration. It also makes starting and stopping the whole environment a single docker compose up command, and keeps logs, volumes, and environment variables in one place.

How Docker Compose Networking Works

When you define multiple services in one Compose file, Docker automatically creates a default network and attaches every service to it. Containers on that network can reach each other using the service name as the hostname. So if your Node.js service is named api, n8n inside its container can call http://api:3000 — no need to know the host machine’s IP or port mapping.

Ports you publish with ports: are only for access from your host machine (e.g., opening n8n in a browser at localhost:5678). Internal communication between containers does not use those published ports; it uses the container’s internal port (the one your Node.js app listens on, usually 3000).

Minimal docker-compose.yml Example

Here is a working starting point. It assumes your Node.js app has a Dockerfile in a subfolder called api.

version: '3.8'

services:
  n8n:
    image: n8nio/n8n
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=localhost
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    volumes:
      - ./api:/usr/src/app
      - /usr/src/app/node_modules

volumes:
  n8n_data:

Key points:

  • depends_on makes Docker start the API before n8n, but it does not wait for the API to be ready — use a healthcheck or a retry in n8n if needed.
  • The API’s volumes: mount your source code for live reload (if you use nodemon or similar).
  • The named volume n8n_data persists n8n’s database and credentials across container restarts.

How n8n Calls the API

Inside n8n, use an HTTP Request node. Set the method (GET, POST, etc.) and the URL to http://api:3000/your-endpoint. Because both containers are on the same network, this resolves correctly. If you need to test from your host browser, use http://localhost:3000 — but inside n8n workflows, always use the service name.

If your API requires authentication, pass it via headers or query parameters in the HTTP Request node. For example, you can store an API key in n8n’s credentials and reference it in the node’s header field.

Common Problems and Fixes

  • Cannot resolve hostname api: Ensure both services are in the same Compose file and not in separate files with different project names. Also check that you are not using network_mode: host, which disables the default network.
  • Connection refused: The API is not listening on the port you expect, or it is still starting. Add a healthcheck to the API service and use depends_on with condition: service_healthy.
  • Port conflicts on host: If port 3000 or 5678 is already used, change the left side of the ports: mapping (e.g., "3001:3000").
  • Node modules not installing: When you mount your source code over the container’s working directory, the container’s own node_modules can be hidden. Use a named volume or an anonymous volume for /usr/src/app/node_modules as shown above.

n8n vs. Node.js API: When to Use Which

n8n excels at visual orchestration: triggering workflows on schedules or webhooks, branching, and connecting to hundreds of pre-built integrations (Slack, Google Sheets, databases, etc.). The Node.js API is better for:

  • Complex business logic with loops, recursion, or heavy computation.
  • Using npm packages not available in n8n’s Code node.
  • Exposing a clean REST interface that other systems (or n8n itself) can call.
  • Unit testing your logic with a proper test framework.

In practice, you often start with n8n nodes, and when a step becomes too convoluted, you move that step into an API endpoint and call it from n8n. This keeps workflows readable while allowing unlimited logic.

Production Considerations

For local development, the example above is fine. For production, you would typically:

  • Add a reverse proxy (like Traefik or nginx) to handle TLS and route to n8n and the API.
  • Use environment variables for secrets instead of hard-coding them in the Compose file.
  • Set N8N_ENCRYPTION_KEY to a persistent value so credentials survive restarts.
  • Use a dedicated database (PostgreSQL) for n8n instead of SQLite, especially with multiple instances.
  • Build the API image with a multi-stage Dockerfile to reduce size and avoid mounting source code.

Frequently Asked Questions

Can I call my Node.js API from n8n using localhost?

No. Inside the n8n container, localhost refers to the n8n container itself, not your host machine. Use the Compose service name (http://api:3000) for inter-container communication. If you absolutely must use localhost, you can set network_mode: host on both services, but that disables Docker’s network isolation and is not recommended.

How do I make n8n wait until my API is ready?

Add a healthcheck to the API service (e.g., using curl or node to hit a health endpoint) and then use depends_on with condition: service_healthy. This is more reliable than a simple depends_on which only waits for the container to start.

Do I need to expose the API port to the host?

Only if you want to test the API directly from your browser or with tools like Postman. n8n can reach the API without any published port. You can omit the ports: mapping for the API and still have n8n use it.

Can I use a separate docker-compose file for the API?

Yes, but you need to manually create a shared external network and set it in both files. It is simpler to keep both services in one file for local development. If you use separate files, run them with docker compose --project-name myproject up to share the same project network.

How do I debug why n8n cannot reach the API?

Run docker compose exec n8n sh and then use wget http://api:3000 or curl inside the container. If that fails, check that the API container is running (docker compose ps) and that it is listening on the correct port. Also verify that both containers are on the same network with docker network inspect.

Is it safe to mount my source code as a volume in production?

No. Mounting source code is a development convenience for hot reload. In production, you should bake the code into the image during the build step. This makes the image self-contained and prevents accidental changes from the host.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →