Self‑Host n8n on Google Cloud (Free Tier) with Docker Compose + Traefik

This guide shows the exact steps I used to self‑host n8n on a Google Cloud VM with a custom domain on Hostinger DNS. It follows the same Docker Compose + Traefik setup as the original tutorial and includes the extra fixes I needed when things didn’t work the first time.

Stack at a glance: Ubuntu VM (e2‑micro) → Docker + Docker Compose plugin → Traefik reverse proxy (auto‑HTTPS via Let’s Encrypt) → n8n container with persistent volumes. No Nginx.

Prerequisites

  • Google Cloud account (Free Tier eligible).
  • A domain you control (examples use example.com; I used Hostinger for DNS).
  • Basic terminal usage (SSH to VM, edit files).

1) Create Your Google Cloud VM (free tier‑friendly)

  1. Console: Go to Google Cloud Console → Compute Engine → VM instances → Create instance.
  2. Namen8n-instance (or similar).
  3. Region/Zone: Choose a free‑tier eligible region if you want to stay free (e.g., us‑west1us‑central1us‑east1). Any region works; free‑tier only matters if you care about zero cost.
  4. Machine typee2-micro is fine for light use.
  5. Boot disk: Ubuntu 22.04 LTS (increase size to 30 GB for comfort).
  6. Firewall: Tick Allow HTTP and Allow HTTPS.
  7. (Optional but recommended) Reserve a static external IP (to avoid DNS breaking after restarts):
    • VPC network → External IP addresses → reserve a static address and attach it to this VM.
  8. Click Create.

Note: In some templates you’ll also see notes about setting a network tag/hostname. It isn’t required for this simple setup.

2) SSH into the VM

Use the SSH → Open in browser window button next to the instance. If you prefer local terminal:

# Replace with your own instance and zone
gcloud compute ssh n8n-instance --zone=us-west1-b

3) Update the OS and install Docker + Docker Compose plugin

sudo apt-get update && sudo apt-get upgrade -y

# Pre-reqs & convenience
sudo apt-get install -y ca-certificates curl nano

# Official Docker repo
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo ${UBUNTU_CODENAME:-$VERSION_CODENAME}) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# (Optional) Run docker as non-root going forward
sudo usermod -aG docker $USER
newgrp docker

docker --version
docker compose version

4) Create a project folder (and fix the missing-folder gotcha)

The tutorial uses a dedicated directory. If cd ~/n8n fails with “No such file or directory”, just create it. I use ~/n8n-compose:

cd ~
mkdir -p n8n-compose
cd ~/n8n-compose

5) Point your domain (Hostinger) at the VM IP

  1. Grab your VM’s External IP from the VM list.
  2. In Hostinger → DNS for your domain, create an A record:
    • Host/Namen8n (or your chosen subdomain)
    • Points to: your VM’s external IP
    • TTL: leave default or 300s
  3. Wait a few minutes. Verify from the VM:
dig +short n8n.example.com

You should see the VM IP. If not, give DNS more time or recheck the record.

6) Environment settings (.env)

Create a .env file that Traefik and n8n will read via Compose labels/environment.

nano .env

Paste and edit:

# Where n8n will be served
DOMAIN_NAME=example.com
SUBDOMAIN=n8n  # results in https://n8n.example.com

# Timezone used for cron/scheduling inside n8n
GENERIC_TIMEZONE=Europe/London

# Email for Let’s Encrypt certificate registration
SSL_EMAIL=[email protected]

Save (Ctrl+OEnter), exit (Ctrl+X).

7) (One-time) Create a shared files directory for workflows

This gives n8n access to a host directory at /files inside the container.

mkdir -p ./local-files
ls -la

8) Docker Compose file (Traefik + n8n)

Create the docker-compose.yml:

nano docker-compose.yml

Paste the following:

services:
  traefik:
    image: traefik
    restart: always
    command:
      - "--api=true"
      - "--api.insecure=true"           # Consider disabling in production
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
      - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
      - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - traefik_data:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    ports:
      - "127.0.0.1:5678:5678"           # internal bind; Traefik handles public 443
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`)
      - traefik.http.routers.n8n.tls=true
      - traefik.http.routers.n8n.entrypoints=web,websecure
      - traefik.http.routers.n8n.tls.certresolver=mytlschallenge
      - traefik.http.middlewares.n8n.headers.SSLRedirect=true
      - traefik.http.middlewares.n8n.headers.STSSeconds=315360000
      - traefik.http.middlewares.n8n.headers.browserXSSFilter=true
      - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
      - traefik.http.middlewares.n8n.headers.forceSTSHeader=true
      - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME}
      - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.n8n.headers.STSPreload=true
      - traefik.http.routers.n8n.middlewares=n8n@docker
    environment:
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files

volumes:
  n8n_data:
  traefik_data:

Save and exit.

9) Launch the stack

# From the ~/n8n-compose directory
sudo docker compose up -d

# Verify containers
docker compose ps

# Tail logs if you need to troubleshoot
docker compose logs -f traefik
docker compose logs -f n8n

Give it ~1–3 minutes on first run (Traefik will obtain certificates).

10) Finish n8n setup in the browser

Open https://n8n.example.com (replace with your domain). You should see the Owner account setup screen. Create your account and (optionally) activate the Fair Code features using your email.

Troubleshooting & Fixes I Needed

A) cd ~/n8n or cd ~/n8n-compose says “No such file or directory”

You’re just in the wrong path or the folder wasn’t created yet. Run:

cd ~ && mkdir -p n8n-compose && cd n8n-compose

B) Domain not resolving / browser can’t reach site

  • Double‑check the Hostinger A record points to the correct VM external IP.
  • Clear local DNS cache or try a different network.
  • Verify from VM:
dig +short n8n.example.com
  • Ensure VM firewall allows 80/443 (we ticked these at create time).
  • If you use any CDN/proxy, disable it until SSL is issued.

C) NET::ERR_CERT_AUTHORITY_INVALID or SSL didn’t issue

  • DNS likely hasn’t propagated or ports 80/443 blocked by another process.
  • Wait a few minutes, then check Traefik logs:
docker compose logs -f traefik
  • Recreate if needed:
docker compose down && docker compose up -d

D) Blank page / 502 from proxy

  • Traefik couldn’t see the n8n container or the labels are wrong.
  • Confirm labels exactly match SUBDOMAIN.DOMAIN_NAME.
  • Confirm n8n is listening internally on 127.0.0.1:5678.
  • Restart both services:
docker compose restart n8n traefik

E) n8n offline after VM reboot

  • Docker not running or containers didn’t come up.
  • We set restart: always, but verify:
systemctl status docker
sudo systemctl enable docker --now

F) “Permission denied” writing to /files inside n8n

  • Ensure the host directory exists (./local-files).
  • Check mount is present: docker compose exec n8n ls -la /files.

Hardening & Nice‑to‑Haves (optional)

  • Lock down Traefik dashboard: We used --api.insecure=true for simplicity. Remove it or protect behind auth/reverse proxy in production.
  • Set an encryption key: Add N8N_ENCRYPTION_KEY in the environment: for the n8n service (a long random string) before you create credentials.
  • Backups: Snapshot the VM or back up the n8n_data volume regularly.
  • Updates:
# Update base OS
sudo apt-get update && sudo apt-get upgrade -y

# Update containers
docker compose pull
docker compose up -d

Leave a Reply

Your email address will not be published. Required fields are marked *