DEV Community

Sanghun Yun
Sanghun Yun

Posted on Originally published at gearflowlab.com

How to Migrate Local Docker & n8n from WSL2 to a $6 DigitalOcean Droplet

Running mission-critical automation workflows (n8n, PostgreSQL, Redis, web scrapers) on Windows 11 via WSL2 inevitably hits two fatal operational bottlenecks:

  1. vmmem RAM Ballooning: The Linux page cache greedily eats 8 to 16 GB of host RAM, starving your IDE and browser.
  2. Offline Webhooks: Whenever your laptop sleeps, reboots for Windows Updates, or drops Wi-Fi, incoming Stripe, GitHub, or CRM webhooks fail with silent timeouts.

In this guide, we'll execute an atomic volume snapshot in WSL2, securely transfer it to a $6/month DigitalOcean Basic Droplet, and configure production Nginx with Let's Encrypt SSL for 24/7 always-on reliability.


Migration Architecture

Here is the migration pipeline from local WSL2 to a hardened cloud droplet:

flowchart TD
    subgraph LocalWSL["1. Local Development Environment (WSL2)"]
        WSL["Local Windows 11 PC<br/>- vmmem RAM exhaustion<br/>- Shuts down when PC sleeps"]
        DockerLocal["Local Docker Containers<br/>(n8n, Postgres, Redis)"]
        Dump["Volume Snapshot Export<br/>docker run --rm -v ... tar -czf"]
    end

    subgraph Transit["2. Secure Network Migration"]
        RSync["Encrypted SSH Sync<br/>rsync -avz -e ssh backup.tar.gz root@droplet:/srv/"]
    end

    subgraph DigitalOcean["3. DigitalOcean Cloud Droplet ($6/mo Standard)"]
        UFW["UFW Firewall Protection<br/>(Open 22, 80, 443 | Block raw app ports)"]
        DockerHost["Docker Engine + Compose<br/>(24/7 Dedicated Cloud Compute)"]

        subgraph Stack["Production Microservices Stack"]
            Nginx["Nginx Reverse Proxy<br/>(Let's Encrypt SSL Auto-Renewal)"]
            N8nProd["n8n Automation Engine<br/>(Webhooks Active 24/7)"]
            DBProd[("Restored Persistent Volumes<br/>(Fast NVMe SSD Storage)")]
        end
    end

    DockerLocal --> Dump
    Dump --> RSync
    RSync --> UFW
    UFW --> DockerHost
    DockerHost --> Nginx
    Nginx -->|HTTPS Reverse Proxy| N8nProd
    N8nProd --> DBProd

Step 1: Provision and Secure the Cloud Droplet

  1. Spin up an Ubuntu 24.04 LTS Droplet on DigitalOcean (Basic $6/mo tier: 1 GB RAM / 1 vCPU / 25 GB NVMe SSD).
  2. SSH into your droplet and configure the host firewall with UFW:
ssh root@<droplet_ip>

# Restrict inbound traffic strictly to SSH, HTTP, and HTTPS
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

Note: Never expose port 5678 (n8n) or 5432 (Postgres) directly to the public internet.


Step 2: Atomic Volume Snapshot in WSL2

Stop your local stack cleanly to guarantee zero open file-lock corruption:

# Inside local WSL2 terminal
cd ~/my-n8n-project
docker compose stop
Enter fullscreen mode Exit fullscreen mode

Create a point-in-time tarball snapshot of your persistent named volume using a disposable Alpine container:

docker run --rm \
  -v n8n_data:/data:ro \
  -v $(pwd):/backup \
  alpine tar -czf /backup/n8n_data_export.tar.gz -C /data .
Enter fullscreen mode Exit fullscreen mode

Step 3: Transfer Volume & Configuration Over SSH

Push the archive, your docker-compose.yml, and your .env file directly to the Droplet:

# Run from local WSL2
scp -P 22 n8n_data_export.tar.gz root@<droplet_ip>:/srv/
scp -P 22 docker-compose.yml root@<droplet_ip>:/srv/
scp -P 22 .env root@<droplet_ip>:/srv/
Enter fullscreen mode Exit fullscreen mode

Step 4: Restore Volume and Boot Production Stack

SSH into the cloud server:

ssh root@<droplet_ip>
cd /srv

# 1. Create target named volume
docker volume create n8n_data

# 2. Extract backup into cloud volume
docker run --rm \
  -v n8n_data:/data \
  -v /srv:/backup \
  alpine tar -xzf /backup/n8n_data_export.tar.gz -C /data
Enter fullscreen mode Exit fullscreen mode

Production docker-compose.yml

version: '3.8'

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    container_name: n8n-automation
    restart: always
    environment:
      - N8N_HOST=n8n.yourdomain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://n8n.yourdomain.com/
      - GENERIC_TIMEZONE=UTC
      - N8N_PAYLOAD_SIZE_MAX=64
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - internal-net

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    networks:
      - internal-net
    depends_on:
      - n8n

networks:
  internal-net:
    driver: bridge

volumes:
  n8n_data:
    external: true
Enter fullscreen mode Exit fullscreen mode

Step 5: Issue SSL and Launch

  1. Point your domain A record (n8n.yourdomain.com) to your Droplet IP.
  2. Issue Let's Encrypt certificates:
   docker run -it --rm \
     -v /srv/certbot/conf:/etc/letsencrypt \
     -v /srv/certbot/www:/var/www/certbot \
     certbot/certbot certonly --webroot -w /var/www/certbot \
     -d n8n.yourdomain.com
Enter fullscreen mode Exit fullscreen mode
  1. Start the stack:
   docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Your workflows, API keys, database connections, and executions are now running 24/7 in the cloud without consuming a single megabyte of memory on your local machine.


Originally published on GearFlow Lab.

Top comments (0)