Setting up a local n8n dev environment on Windows using WSL, Docker Desktop, AlmaLinux and Cloudflare Tunnel
Note: this article is still a Work In Progress (WIP) detailing the steps I took setting up my own n8n local development environment in my Windows machine. The goal for now is for the article to serve as a reference for personal use. I do plan to update and make it readable and helpful as well to readers and other folks who might be trying to do the same thing.
1. Dev environment on Windows using WSL, Docker Desktop, AlmaLinux, n8n, Cloudflare Tunnel
1.1 Install WSL and AlmaLinux
- In PowerShell as Administrator:
wsl --install --no-distribution
Reboot if prompted.
- After reboot, list available distros:
wsl --list --online
Install AlmaLinux 9:
wsl --install AlmaLinux-9
Complete the first‑run setup (Linux username/password).
- Ensure it runs as WSL2:
wsl --list --verbose
If AlmaLinux isn’t VERSION 2:
wsl --set-version AlmaLinux-9 2
1.2 Configure Docker Desktop with WSL
-
Install Docker Desktop for Windows.
-
In Docker Desktop → Settings → Resources → WSL integration:
Enable integration for AlmaLinux-9.
-
Open an AlmaLinux shell from Windows Terminal or Start menu.
1.3 Project layout & env files
In project folder (for example, C:\Users\romme\OneDrive\Documents\Projects\n8n), layout should be something like below:
n8n/
docker-compose.yml # base
docker-compose.dev.yml # dev overlay
.env.dev # dev env vars
.cloudflared.env # Cloudflare tunnel token
Makefile
data/ # created by n8n
.env.dev (example):
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=...
N8N_BASIC_AUTH_PASSWORD=...
N8N_HOST=dev-n8n.example.com
WEBHOOK_URL=https://dev-n8n.example.com/
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=dev-n8n-pgdb-user
DB_POSTGRESDB_PASSWORD=...
POSTGRES_DB=n8n
POSTGRES_USER=dev-n8n-pgdb-user
POSTGRES_PASSWORD=...
1.4 Base docker-compose (n8n + Postgres)
docker-compose.yml:
services:
n8n:
image: n8nio/n8n:2.1.1
restart: always
ports:
- "5678:5678"
environment:
- N8N_BASIC_AUTH_ACTIVE=${N8N_BASIC_AUTH_ACTIVE}
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
- N8N_HOST=${N8N_HOST}
- N8N_PORT=5678
- WEBHOOK_URL=${WEBHOOK_URL}
- EXECUTIONS_PROCESS=main
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=${DB_POSTGRESDB_HOST}
- DB_POSTGRESDB_PORT=${DB_POSTGRESDB_PORT}
- DB_POSTGRESDB_DATABASE=${DB_POSTGRESDB_DATABASE}
- DB_POSTGRESDB_USER=${DB_POSTGRESDB_USER}
- DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
networks:
- n8n_network
postgres:
image: postgres:13
restart: always
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_postgres_data:/var/lib/postgresql/data
networks:
- n8n_network
volumes:
n8n_data:
n8n_postgres_data:
networks:
n8n_network:
driver: bridge
This file is shared by dev/QA/prod; differences come through env files and overlays.
1.5 Dev overlay with Cloudflare Tunnel
docker-compose.dev.yml:
services:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel run
env_file:
- .cloudflared.env
depends_on:
- n8n
networks:
- n8n_network
.cloudflared.env:
TUNNEL_TOKEN=your-cloudflare-tunnel-token
On the Cloudflare side:
-
Create a tunnel in Cloudflare Zero Trust → Access → Tunnels.
-
Configure a Public hostname:
Hostname: dev-n8n.example.com
Type: HTTP
Service URL: n8n:5678 (no http://).
Cloudflare’s DNS gets a CNAME for dev-n8n.example.com pointing to the tunnel.
1.6 Running the dev stack from AlmaLinux WSL
From AlmaLinux in the project directory:
docker compose \
-f docker-compose.yml \
-f docker-compose.dev.yml \
--env-file .env.dev up -d
Then access:
https://n8n-dev.0xrommel.dev
Cloudflare Tunnel forwards that to the local n8n container.
2. Best practices for docker-compose files
-
Base + overlays pattern
-
docker-compose.yml for shared configuration.
-
docker-compose.dev.yml, docker-compose.prod.yml for environment‑specific overrides (labels, extra services, debug tools, tunnels, ingress).
-
-
Use env files, never hard‑code secrets
-
Reference variables in environment: using ${VAR}.
-
Commit .example env files; ignore real .env. in Git.
-
-
Separate concerns via networks
-
Internal app DB network (n8n_network).
-
Optional external/proxy network (traefik_default) marked as external: true only when you actually have a Traefik stack creating it.
-
-
Use official images where possible
- n8nio/n8n and postgres:13 straight from Docker Hub for simplicity and updates.
-
Port mappings and health
-
Explicit ports in dev (“5678:5678”) so you can reach services directly.
-
In prod, you can drop host ports and only expose via proxy (Traefik/Ingress).
-
-
Don’t over‑customize the app image unless necessary
- For this dev setup, you dropped the custom Dockerfile and used the official n8n image, avoiding base‑OS/package‑manager issues and keeping parity with upstream.
3. Makefile to simplify Docker commands
Instead of typing the full compose command, you added a Makefile.
Makefile:
DEV_COMPOSE_FILES = -f docker-compose.yml -f docker-compose.dev.yml
DEV_ENV_FILE = --env-file .env.dev
dev-up:
docker compose $(DEV_COMPOSE_FILES) $(DEV_ENV_FILE) up -d
dev-down:
docker compose $(DEV_COMPOSE_FILES) $(DEV_ENV_FILE) down
dev-logs:
docker compose $(DEV_COMPOSE_FILES) $(DEV_ENV_FILE) logs -f
dev-ps:
docker compose $(DEV_COMPOSE_FILES) $(DEV_ENV_FILE) ps
Use it from AlmaLinux WSL:
make dev-up # start n8n + postgres + cloudflared
make dev-logs # follow logs
make dev-ps # see status
make dev-down # stop everything
On AlmaLinux, install make once if needed:
sudo dnf install -y make
Voila! In the end, you get:
- A Windows dev environment that runs Linux containers via WSL2.
- n8n + Postgres matching how you’ll deploy on AlmaLinux.
- Cloudflare Tunnel providing a real HTTPS dev URL.
- Clean, versioned Compose files with environment overlays.
- A Makefile you can use to spin the stack up and down with a single, memorable command.