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_onmakes 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_datapersists 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 usingnetwork_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_onwithcondition: 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_modulescan be hidden. Use a named volume or an anonymous volume for/usr/src/app/node_modulesas 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_KEYto 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
- Backup Not Enough Space? Fix It Now200.00 GB
- Approximate Match Lookup: Conditional Formatting
- GST on Delivery Charges in India: E-Commerce Rules
- NFC Reader on Your Phone: 9 Things You’re Not Doing
- Smart TV DNS Settings: Why They Change & How to Fix
- RabbitMQ Hosting for Startups: Managed vs Self-Hosted