Possible optimization of the docker-compose.yml in the Hetzner server setup tutorial

Hi everyone,

this is the first time I am trying to set up a self-hosted n8n installation on the Hetzner cloud heaviliy relying on your tutorial, the associated GitHub repo and the Docker documentation. This has left me with a few questions concerning the example docker-compose.yml file (especially the mounted volumes).

I understand there are two types of volumes:

  1. “Config volumes” which are used to pass configurations from the host to the containers (namely “caddy_config” and “Caddyfile”)

  2. “Persistent volumes” which are created to allow n8n and Caddy to store data (namely “caddy_data”, “local_files” and “.n8n”)

So my first 2 questions are:

• Since config volumes only need to be read by the hosts, wouldn’t it be “safer” to make them read-only using “:ro”?
• And for persistent volumes, wouldn’t it be “safer” to use Docker volumes instead of bind mounts, since they are then handled by Docker and not directly by the file system (Volumes | Docker Documentation)?

My last question is concerning the 2 named volumes defined globally at the end of the file (external “caddy_data” and “caddy_config”). If I understand things right, the named volumes are defined but not used by any services. So:

• Can the named volumes be left out?

To sum things up, is the following docker-compose.yml file an optimized version of the current one (assuming caddy_data has not been initiated by Docker before)?

version: "3.7"

services:

caddy:
image: caddy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- caddy_data:/data # <- now uses (named) docker volume
- ${DATA_FOLDER}/caddy_config:/config:ro # <- now has read-only flag
- ${DATA_FOLDER}/caddy_config/Caddyfile:/etc/caddy/Caddyfile:ro # <- now has read-only flag

n8n:
image: n8nio/n8n
restart: always
ports:
- 5678:5678
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER
- N8N_BASIC_AUTH_PASSWORD
- 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:
- local_files:/files # <- now uses (named) docker volume
- n8n:/home/node/.n8n # <- now uses (named) docker volume

volumes: # Now only defines named docker volumes in use, "caddy_config" is left out
caddy_data:
local_files:
n8n:

As I said: I am very new to this, so please have mercy in case my questions do not make sense. Your help and thoughts are highly appreciated. :slight_smile:

1 Like

I am not a docker expert but I do see some things that dont make sense in the sample docker file.
I have made some changes like adding both caddy and n8n to an explicit subnet (updates kept changing the internal IPs and breaking the caddy which I used for other docker containers too)
It is very easy to run multiple domains and other apps using the n8n caddy config.

@AdFisch - did these changes work for you?

Here are some of my mods

version: "3.7"

services:
  caddy:
    image: caddy:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ${DATA_FOLDER}/caddy_data:/data
      - ${DATA_FOLDER}/caddy_config:/config
      - ${DATA_FOLDER}/caddy_config/Caddyfile:/etc/caddy/Caddyfile

    extra_hosts:
      - "host.docker.internal:host-gateway"

    networks:
      network:
        ipv4_address: 10.5.0.5


  n8n:
    image: n8nio/n8n
    restart: always
    ports:
      - 5678:5678
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER
      - N8N_BASIC_AUTH_PASSWORD
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - N8N_EMAIL_MODE=smtp
      - N8N_SMTP_HOST=smtp.XXXX.XXX
      - N8N_SMTP_PORT=465
      - N8N_SMTP_SSL=true
      - N8N_SMTP_USER=XXXXXXXXXXXX
      - N8N_SMTP_PASS=XXXXXXXXXXXXXXXXXXXXXX
      - N8N_SMTP_SENDER=n8n [email protected]
      - EXECUTIONS_PROCESS=main
      - EXECUTIONS_DATA_MAX_AGE=5
      - DB_SQLITE_VACUUM_ON_STARTUP=true
    volumes:
      - ${DATA_FOLDER}/.n8n:/home/node/.n8n
      - ${DATA_FOLDER}/local_files:/files

    networks:
      network:
        ipv4_address: 10.5.0.6

volumes:
  caddy_data:
    external: true
  caddy_config:

networks:
  network:
    driver: bridge
    ipam:
      config:
        - subnet: 10.5.0.0/16
          gateway: 10.5.0.1

Hi @treyr,

thanks for your answer and sorry for my late reply.

Concerning your changes
I cannot remember where I read about it but the behavior you described (change of IP address on container updates) is documented and normal/expected. However, I do not see a problem here if you configure Caddy as described in the tutorial. Caddy’s reverse_proxy directive does not point to an IP address but rather the container name “n8n” which remains the same. At least it works for me.

Volumes
I applied my suggestions and everything seems to work: For the “configuration volumes” I still use bind mounts but with the :ro flag. For the “persistant volumes”, however, I switched to Docker volumes as described in my first post.

One more thing: ports vs. expose
In the definition of the n8n container I now use the “expose” instead of the “ports” directive. The difference is that with expose ports are only made available in the local network but not the host machine. And behind a reverse proxy it does not make sense to also expose port 5678 to the public because all Caddy directives (including potential security measures) will not be available on that port.

Summary
To sum things up, I think the tutorial file should look like this (and it works for me). Hope this helps. However as stated before, I am not an expert and in case anyone finds improvements I appreciate your note. Thank you.

  version: "3.7"

  services:

    caddy:
      image: caddy:latest
      restart: unless-stopped
      ports:
        - "80:80"
        - "443:443"
      volumes:
        - caddy_data:/data # <- now uses (named) Docker volume
        - ${DATA_FOLDER}/caddy_config:/config:ro # <- now has read-only flag
        - ${DATA_FOLDER}/caddy_config/Caddyfile:/etc/caddy/Caddyfile:ro # <- now has read-only flag

    n8n:
      image: n8nio/n8n
      restart: always
      expose: # <- now uses expose instead of ports
        - "5678"
      environment:
        # I also recommend reading the full documentation about environment variables here
        # to further enhance security (e. g. disabling risky nodes like
        # "read binary file" or "execute command")
        - N8N_BASIC_AUTH_ACTIVE=true
        - N8N_BASIC_AUTH_USER
        - N8N_BASIC_AUTH_PASSWORD
        - 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:
        - local_files:/files # <- now uses (named) Docker volume
        - n8n:/home/node/.n8n # <- now uses (named) Docker volume

  volumes: # Now only defines named Docker volumes in use, "caddy_config" is left out
    caddy_data:
    local_files:
    n8n:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.