Ultimate n8n Dev Container (with Debugging, Monitoring & Observability) Dockers

Hey n8n fam :wave:,

I wanted to share my Docker-based full dev container setup I’ve been building to make working with n8n smoother, especially when working with queues, databases, and performance tuning.

:bulb: Key Features
:white_check_mark: n8n + n8n-worker (with queue mode support) Disabled in Dockerfile, enable by removing comments
:white_check_mark: PostgreSQL + pgAdmin
:white_check_mark: Redis + RedisInsight
:white_check_mark: Bull Board for queue debugging
:white_check_mark: Prometheus + Grafana for metrics
:white_check_mark: Clean volume separation for data persistence
:white_check_mark: .env-driven configuration for secrets
:white_check_mark: Health checks and restart policies for dev reliability

:whale: Services

Service Description Port
n8n Main n8n app 5678
n8n-worker Executes jobs in queue mode
PostgreSQL Database backend 5432
pgAdmin PostgreSQL GUI 5050
Redis Queue engine 6379
RedisInsight GUI for Redis debugging 8001
Bull Board Monitor Bull queues (n8n jobs) 3002
Prometheus Metrics collector 9090
Grafana Dashboards for observability 3003

File in folder

For Grafana and Prometheus see here details

docker-compose.yml

version: '3.8'

volumes:
  db_storage:
  n8n_storage:
  redis_storage:
  pgadmin_data:
  prometheus_data:
  grafana_data:

x-shared: &shared
  restart: always
  image: docker.n8n.io/n8nio/n8n
  environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_PORT=5432
    - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
    - DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
    ##- EXECUTIONS_MODE=queue
    ##- QUEUE_MODE=redis
    ##- QUEUE_BULL_REDIS_HOST=redis
    ##- QUEUE_BULL_REDIS_PORT=6379
    ##- QUEUE_HEALTH_CHECK_ACTIVE=true
    - N8N_DEFAULT_BINARY_DATA_MODE=filesystem
    - N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
    - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
    - N8N_HOST=localhost
    - N8N_PORT=5678
    - N8N_PROTOCOL=http
    - N8N_BASIC_AUTH_ACTIVE=true
    - N8N_BASIC_AUTH_USER=${N8N_USER}
    - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
    - N8N_LOG_OUTPUT=file
    - n8n.log.level=debug
    - N8N_METRICS=true
    - N8N_RUNNERS_ENABLED=true	
  links:
    - postgres
    - redis
  volumes:
    - n8n_storage:/home/node/.n8n
  depends_on:
    redis:
      condition: service_healthy
    postgres:
      condition: service_healthy

services:
  postgres:
    image: postgres:16
    restart: always
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
      - POSTGRES_NON_ROOT_USER
      - POSTGRES_NON_ROOT_PASSWORD
    volumes:
      - db_storage:/var/lib/postgresql/data
      - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

  redis:
    image: redis:6-alpine
    restart: always
    volumes:
      - redis_storage:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      timeout: 5s
      retries: 10

  n8n:
    <<: *shared
    ports:
      - 5678:5678
    user: root

  n8n-worker:
    <<: *shared
    command: worker
    depends_on:
      - n8n

  redisinsight:
    image: redislabs/redisinsight:1.14.0
    container_name: redisinsight
    ports:
      - "8001:8001"
    restart: always
    depends_on:
      redis:
        condition: service_healthy

  bull-board:
    build:
      context: .
      dockerfile: Dockerfile.bullboard
    container_name: bull-board
    ports:
      - "3002:3002"
    environment:
      - REDIS_HOST=redis
    depends_on:
      redis:
        condition: service_healthy

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    restart: always
    ports:
      - "5050:80"
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: [email protected]
      PGADMIN_DEFAULT_PASSWORD: admin
    depends_on:
      postgres:
        condition: service_healthy

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    restart: always
    ports:
      - "9090:9090"
    volumes:
      - prometheus_data:/prometheus
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
    depends_on:
      - n8n

  grafana:
    image: grafana/grafana
    container_name: grafana
    restart: always
    ports:
      - "3003:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus

Dockerfile.bullboard

FROM node:18-alpine

WORKDIR /app

# Install all necessary packages
RUN npm install express @bull-board/api @bull-board/ui @bull-board/express bull ioredis

# Add the minimal express server
COPY server.js .

EXPOSE 3002

CMD ["node", "server.js"]

server.js

const express = require('express');
const { createBullBoard } = require('@bull-board/api');
const { BullAdapter } = require('@bull-board/api/bullAdapter');
const { ExpressAdapter } = require('@bull-board/express');
const Queue = require('bull');

// Setup the Bull Board UI using Express
const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath('/ui');

// This must match the queue name and prefix used by n8n
const jobsQueue = new Queue('jobs', {
  redis: {
    host: process.env.REDIS_HOST || 'redis',
    port: 6379,
  },
  prefix: 'bull' // <-- very important: this is what n8n uses
});

// Register queues with Bull Board
const { addQueue, removeQueue, setQueues, replaceQueues } = createBullBoard({
  queues: [new BullAdapter(jobsQueue)],
  serverAdapter,
});

// Start the Express server
const app = express();
app.use('/ui', serverAdapter.getRouter());

app.listen(3002, () => {
  console.log('🚀 Bull Board is running at http://localhost:3002/ui');
});


:handshake: Contributions & Ideas Welcome

Open to suggestions, tweaks, or contributions. If you’re running more services, feel free to extend this setup and share yours!

Cheers,
@King_Samuel_David

btw, I spin up other versions for testing like this:

docker run -d
–name n8n
–user root
-p 0:5678
-v “$(pwd)”:/home/node/.n8n
-e N8N_ENCRYPTION_KEY=‘KEY’
-e N8N_BASIC_AUTH_ACTIVE=false
n8nio/n8n:1.71.0

3 Likes

As someone just figuring this stuff out on his own without much expertise this stuff is just so insightful for my learning journey. Really appreciate you sharing it.

1 Like

I’ll leave this message so that the post won’t be deleted for a long time
because it’s cool

1 Like

ahh thanks :slight_smile: hope all going well.