Tom's Docker Setup: Simple, Clean, and Easy to Back Up

Thanks @LTS_Tom this post has been helpful as I slowly expand my docker setup. I would love to see a revisit on this video. At the time, there were no official docker images, but they now have several.

I tried rebuilding my container using their guide and got pretty lost. For instance, they have you create a .env file, but then I don’t see it referenced in the compose.yml anywhere.

I’ll keep using my container I setup following your guide until then.

Nothing has really changed other than a few apps. Not clear on your FreshRSS question. Here is my compose set up for FreshRSS :

services:                                                                                                                           
  freshrss:                                                                                                                         
    image: freshrss/freshrss:edge    
    container_name: freshrss    
    restart: unless-stopped    
    environment:    
      - TZ=America/Detroit    
      - CRON_MIN=*/20    
    ports:    
      - "10010:80"    
    volumes:    
      - ./data:/var/www/FreshRSS/data    
      - ./extensions:/var/www/FreshRSS/extensions    

First, thanks for your quick reply. I really appreciate you looking at this with me.

This might just all be due to a lack of understanding of how docker works. In fact, I learned a bit while composing this reply.

Their github page defaults to the edge branch, which seems unusual to me. I assumed most people would want to run the stable (latest) version, but it looks like that’s what you’re running, so maybe that’s not unusual?

When I navigate to their docker install page on latest branch and then to the docker compose installation section, it starts with setting up the .env file, then setting up the docker-compose.yml.

I don’t see any calls to a .env in your compose.

Their “partial” compose file in the instruction section is massive, and contains several calls to the .env, though most of these are for variables that aren’t used if using the default SQLite DB.

The compose file posted in the repo is a lot smaller, but it doesn’t seem to reference the .env file at all.

I thought there was a problem with volumes. The volumes are declared in the service, but I thought they needed to be declared separately too. Nope, after looking up the documentation, that’s only if using the volumes across multiple services.

I’m trying to migrate from the old unsupported linuxserver.io setup to the official image. I don’t like to run things without understanding what they do and I’m struggling to understand what I’m setting up here.

I’m not trying to get you to support their software, but if you have any insight into what I’m missing or not understanding that would be appreciated. Thanks.

It’s up to you if you want to run the edge version, use stable if you are concerned ( I have not had any issues). You can use my compose file posted above or follow the instructions for theirs. I did not look so I am not sure why they have an .env file.

1 Like

I don’t see any calls to a .env in your compose.

You’re right, Tom’s compose file does not use an external .env file, and the basic docker-compose.yml on the FreshRSS GitHub also does not use an external .env file. As you noticed, most of the file is not required when using the SQLite DB and you have some of the variables specified in the compose file.

I think the documentation confusion comes from some magic Docker is doing that you likely do not know about. If you have a .env file in the same directory as the compose file Docker will automatically pull that file in at run time but, and this is important, the variables it contains will NOT automatically be passed to the containers environment variables. They are just able to be referenced within the compose file itself. If we look at this section from their documentation,

FRESHRSS_INSTALL: |-
        --api-enabled
        --base-url ${BASE_URL}
        --db-base ${DB_BASE}
        --db-host ${DB_HOST}
        --db-password ${DB_PASSWORD}
        --db-type pgsql
        --db-user ${DB_USER}
        --default-user admin
        --language en

you will see that the variables in the .env file are referenced in the form of ${VARIABLE}. This is what they are using the .env for. They could have just written,

FRESHRSS_INSTALL: |-
        --api-enabled
        --base-url https://freshrss.example.net
        --db-base freshrss
        --db-host freshrss-db
        --db-password freshrss
        --db-type pgsql
        --db-user freshrss
        --default-user admin
        --language en

In short, you’re mostly fine to use Tom’s compose file. Although, I would recommend that you add a logging section like this,

services:                                                                                                                           
  freshrss:                                                                                                                         
    image: freshrss/freshrss:edge    
    container_name: freshrss    
    restart: unless-stopped    
    environment:    
      - TZ=America/Detroit    
      - CRON_MIN=*/20    
    ports:    
      - "10010:80"    
    volumes:    
      - ./data:/var/www/FreshRSS/data    
      - ./extensions:/var/www/FreshRSS/extensions
    logging:
      options:
        max-size: 10m

By default Docker does not truncate logs, so the host will eventually run out of disk space. That is unless the container is regularly destroyed (not just restarted) and recreated. This section tells Docker to limit the log to 10MB.

1 Like

Thank you for this detailed reply. I watch videos, read the source docs, and compare installs between containers to try understand how things work, and explanations like this are super helpful.

I use immich as well and knew I used a .env file with it so I compared their docker-compose.yml and saw they declare the .env within each service in addition to calling variables with the ${VARIABLE} like you showed.

I didn’t realize that docker pulled in the .env regardless and only references it when a variable is called.

I’m guessing it’s like programming languages, where it might be best practice to comment your code or use camelCase for variables. It will still work without following these guidelines.

I also hadn’t realized that about the log files. I’ll definitely add that to limit their size.

I need to read up on what is involved in destroying a container vs my typical update process of pulling the new image, bringing them up, and then pruning old images. I might need to add that to other containers after checking the size of their logs.

I would also add to the log truncating “max-file”. Even though you specify the “max-size” you can still end up with your disk filling up by not limiting the number of log files. So something like this:

    logging:
      options:
        max-size: 10m
        max-file: "5"

1 Like

@twdougan Yes, it can be a bit like a programming language. Lots of things you should do, but you rarely find an example with all of those things. When you’re new you don’t know what you don’t know, so it is often difficult to understand what is important and what isn’t. It also doesn’t help when some examples/documentation are just plain wrong, but because it is on the ‘Internet’ the person writing it must know what they’re doing, right?

@angriness5448 Thanks, that is a very good point that I missed off.

I really liked Tom’s setup in this thread, so I built it as a single compose stack that keeps his “simple, clean, easy to back up” vibe, but swaps Nginx Proxy Manager for Traefik.

Traefik feels like a better fit here because it’s very container‑native: it watches Docker directly, so as soon as you spin up a service with the right labels it’s online behind the proxy, and Let’s Encrypt is just part of the config instead of something you click around a UI to wire up. All the routing, TLS, and SSO stuff live right next to each service in the compose file, which makes it much easier to manage.

I also wired in Authelia so you can put SSO in front of the various services like Homarr, IT Tools, CyberChef, Netdata, Dozzle, WUD, etc. while letting stuff like Jellyfin or Guacamole keep their own auth or stay internal if you prefer. BTW, Guacamole will use OIDC to authenticate with Authelia if you want. DNS and domains are handled via a .env-template (with DOMAIN, LE_EMAIL, TZ, and Traefik dashboard BasicAuth). Let’s Encrypt is pointed at staging by default so you can test everything without tripping rate limits; once it all looks good you flip it over to production.

WUD is in the stack for update visibility, but I left Watchtower out on purpose. If someone wants auto‑updates they can run Watchtower (I included a docker-compose.yml for it below) in a tiny separate compose and opt‑in only the containers they’re comfortable updating automatically.

Below I’ve included the .env-template, the single docker-compose.yml with all the apps Tom mentioned, and an optional docker-compose.yml for watchtower in case anyone wants to leverage it for auto updates. Be aware Watchtower can mess up some containers so choose wisely which ones you want auto updated.

docker-compose.yml:

# ------------------------------------------------------------------------------
# Traefik + Authelia Docker Compose Stack
#
# First-run setup:
# 1) Copy .env-template to .env
# 2) Edit .env and set at minimum:
#      TZ=America/New_York
#      DOMAIN=yourdomain.tld
#      LE_EMAIL=you@yourdomain.tld
#      BASIC_AUTH_USERS=admin:$apr1$... or bcrypt hash
# 3) Create public DNS records for every hostname used by this stack before
#    testing certificates, for example:
#      traefik.${DOMAIN}
#      auth.${DOMAIN}
#      homarr.${DOMAIN}
#      rss.${DOMAIN}
#      ladder.${DOMAIN}
#      ai.${DOMAIN}
#      tools.${DOMAIN}
#      chef.${DOMAIN}
#      netalertx.${DOMAIN}
#      stats.${DOMAIN}
#      guac.${DOMAIN}
#      speed.${DOMAIN}
#      logs.${DOMAIN}
#      wud.${DOMAIN}
#      media.${DOMAIN}
# 4) Create the external Docker network if it does not exist:
#      docker network create proxy
# 5) Create required folders:
#      mkdir -p \
#        ./data/traefik/letsencrypt \
#        ./data/traefik/logs \
#        ./data/traefik/dynamic \
#        ./data/authelia/config \
#        ./data/homarr/config \
#        ./data/freshrss/config \
#        ./data/freshrss/data \
#        ./data/openwebui \
#        ./data/netalertx/config \
#        ./data/netalertx/db \
#        ./data/netdata/config \
#        ./data/netdata/lib \
#        ./data/netdata/cache \
#        ./data/wud \
#        ./data/jellyfin/config \
#        ./data/jellyfin/media
# 6) Create ACME storage and lock down permissions:
#      touch ./data/traefik/letsencrypt/acme.json
#      chmod 600 ./data/traefik/letsencrypt/acme.json
# 7) Optional: create dynamic config files under:
#      ./data/traefik/dynamic/
# 8) Leave Let's Encrypt staging enabled for initial testing so you can confirm
#    routing, DNS, and challenge handling without hitting production rate limits.
# 9) After staging works, comment out the staging caServer line below and restart
#    Traefik to request real production certificates.
#
# Example dynamic config file:
#   ./data/traefik/dynamic/tls.yaml
#   ---
#   tls:
#     options:
#       default:
#         minVersion: VersionTLS12
#         sniStrict: true
#
# Traefik dashboard BasicAuth:
# 1) Generate a password hash with htpasswd, for example:
#      htpasswd -nbB admin 'YourStrongPassword'
#    or:
#      htpasswd -nbm admin 'YourStrongPassword'
# 2) If placing the hash directly in docker-compose labels, escape $ as $$.
# 3) In this stack, the dashboard auth is read from BASIC_AUTH_USERS in .env.
#
# Updates:
# - WUD in this stack provides update awareness and notifications.
# - Watchtower is NOT included in this stack by default.
# - If you want automatic updates later, it is recommended to run Watchtower
#   as a separate stack and enable it only for selected containers using:
#     com.centurylinklabs.watchtower.enable=true
# - Recommended Watchtower mode:
#     WATCHTOWER_LABEL_ENABLE=true
#   so only explicitly labeled containers are auto-updated.
#
# Notes:
# - The dashboard auth protects only Traefik’s dashboard route.
# - Any label using authelia@docker can be commented out if you do not want SSO
#   through Authelia on that specific service.
# ------------------------------------------------------------------------------

services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy:latest
    container_name: traefik_socket-proxy
    restart: always
    security_opt:
      - no-new-privileges:true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      LOG_LEVEL: info
      CONTAINERS: 1
      NETWORKS: 1
      INFO: 1
      VERSION: 1
      EVENTS: 1
      PING: 1
      AUTH: 0
      BUILD: 0
      COMMIT: 0
      CONFIGS: 0
      DISTRIBUTION: 0
      EXEC: 0
      IMAGES: 0
      NODES: 0
      PLUGINS: 0
      POST: 0
      SECRETS: 0
      SERVICES: 0
      SESSION: 0
      SWARM: 0
      SYSTEM: 0
      TASKS: 0
      VOLUMES: 0
    expose:
      - "2375"
    networks:
      - proxy

  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: always
    depends_on:
      - socket-proxy
    env_file:
      - .env
    environment:
      TZ: ${TZ}

      # Uncomment and fill only if using ACME DNS challenge with Cloudflare
      # CF_API_EMAIL: ${CF_API_EMAIL}
      # CF_API_KEY: ${CF_API_KEY}
      # CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}

      # Uncomment and fill only if using ACME DNS challenge with DNSMadeEasy
      # DNSMADEEASY_API_KEY: ${DNSMADEEASY_API_KEY}
      # DNSMADEEASY_API_SECRET: ${DNSMADEEASY_API_SECRET}

      # Uncomment and fill only if using ACME DNS challenge with GoDaddy
      # GODADDY_API_KEY: ${GODADDY_API_KEY}
      # GODADDY_API_SECRET: ${GODADDY_API_SECRET}
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.endpoint=tcp://socket-proxy:2375"
      - "--providers.docker.watch=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"

      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"

      - "--entrypoints.http.address=:80"
      - "--entrypoints.https.address=:443"

      # Uncomment below to redirect http to https globally
      # - "--entrypoints.http.http.redirections.entryPoint.to=https"
      # - "--entrypoints.http.http.redirections.entryPoint.scheme=https"

      - "--serversTransport.insecureSkipVerify=true"
      - "--log=true"
      - "--log.level=DEBUG"
      - "--accessLog=true"
      - "--accessLog.filePath=/var/log/docker/traefik.log"
      - "--accessLog.bufferingSize=100"

      # Leave staging enabled for initial testing, then comment it out for prod.
      - "--certificatesResolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesResolvers.le.acme.httpChallenge=true"
      - "--certificatesResolvers.le.acme.httpChallenge.entryPoint=http"

      # DNS challenge options, uncomment only if switching from HTTP to DNS challenge
      # - "--certificatesResolvers.le.acme.dnsChallenge=true"
      # - "--certificatesResolvers.le.acme.dnsChallenge.provider=cloudflare"
      # - "--certificatesResolvers.le.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
      # - "--certificatesResolvers.le.acme.dnsChallenge.delayBeforeCheck=120"

      - "--certificatesResolvers.le.acme.email=${LE_EMAIL}"
      - "--certificatesResolvers.le.acme.storage=/etc/traefik/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      # Uncomment for local dashboard troubleshooting only
      # - "8080:8080"
    volumes:
      - ./data/traefik/letsencrypt:/etc/traefik/letsencrypt
      - ./data/traefik/logs:/var/log/docker
      - ./data/traefik/dynamic:/etc/traefik/dynamic:ro
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.traefik.entrypoints=http"
      - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.${DOMAIN}`)"
      - "traefik.http.routers.traefik-secure.tls=true"
      - "traefik.http.routers.traefik-secure.tls.certresolver=le"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth@docker"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${BASIC_AUTH_USERS}"

  authelia:
    image: authelia/authelia:latest
    container_name: authelia
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    volumes:
      - ./data/authelia/config:/config
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.authelia.entrypoints=http"
      - "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN}`)"
      - "traefik.http.routers.authelia.middlewares=authelia-https-redirect"
      - "traefik.http.routers.authelia-secure.entrypoints=https"
      - "traefik.http.routers.authelia-secure.rule=Host(`auth.${DOMAIN}`)"
      - "traefik.http.routers.authelia-secure.tls=true"
      - "traefik.http.routers.authelia-secure.tls.certresolver=le"
      - "traefik.http.routers.authelia-secure.service=authelia"
      - "traefik.http.services.authelia.loadbalancer.server.port=9091"
      - "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/authz/forward-auth"
      - "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true"
      - "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
      - "traefik.http.middlewares.authelia-https-redirect.redirectscheme.scheme=https"

  homarr:
    image: ghcr.io/homarr-labs/homarr:latest
    container_name: homarr
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
      SECRET_ENCRYPTION_KEY: replace_me
    volumes:
      - ./data/homarr/config:/appdata
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    # ports:
    #   - "7575:7575" # Enable for troubleshooting
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.homarr.entrypoints=http"
      - "traefik.http.routers.homarr.rule=Host(`homarr.${DOMAIN}`)"
      - "traefik.http.routers.homarr.middlewares=homarr-https-redirect"
      - "traefik.http.routers.homarr-secure.entrypoints=https"
      - "traefik.http.routers.homarr-secure.rule=Host(`homarr.${DOMAIN}`)"
      - "traefik.http.routers.homarr-secure.tls=true"
      - "traefik.http.routers.homarr-secure.tls.certresolver=le"
      - "traefik.http.routers.homarr-secure.service=homarr"
      - "traefik.http.routers.homarr-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.homarr.loadbalancer.server.port=7575"

  freshrss:
    image: freshrss/freshrss:latest
    container_name: freshrss
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
      PUID: 999
      PGID: 999
    volumes:
      - ./data/freshrss/config:/config
      - ./data/freshrss/data:/var/www/FreshRSS/data
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.freshrss.entrypoints=http"
      - "traefik.http.routers.freshrss.rule=Host(`rss.${DOMAIN}`)"
      - "traefik.http.routers.freshrss.middlewares=freshrss-https-redirect"
      - "traefik.http.routers.freshrss-secure.entrypoints=https"
      - "traefik.http.routers.freshrss-secure.rule=Host(`rss.${DOMAIN}`)"
      - "traefik.http.routers.freshrss-secure.tls=true"
      - "traefik.http.routers.freshrss-secure.tls.certresolver=le"
      - "traefik.http.routers.freshrss-secure.service=freshrss"
      - "traefik.http.routers.freshrss-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.freshrss.loadbalancer.server.port=80"

  ladder13:
    image: ghcr.io/wasi-master/13ft:latest
    container_name: ladder13
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.ladder13.entrypoints=http"
      - "traefik.http.routers.ladder13.rule=Host(`ladder.${DOMAIN}`)"
      - "traefik.http.routers.ladder13.middlewares=ladder13-https-redirect"
      - "traefik.http.routers.ladder13-secure.entrypoints=https"
      - "traefik.http.routers.ladder13-secure.rule=Host(`ladder.${DOMAIN}`)"
      - "traefik.http.routers.ladder13-secure.tls=true"
      - "traefik.http.routers.ladder13-secure.tls.certresolver=le"
      - "traefik.http.routers.ladder13-secure.service=ladder13"
      - "traefik.http.routers.ladder13-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.ladder13.loadbalancer.server.port=5000"

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    volumes:
      - ./data/openwebui:/app/backend/data
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.openwebui.entrypoints=http"
      - "traefik.http.routers.openwebui.rule=Host(`ai.${DOMAIN}`)"
      - "traefik.http.routers.openwebui.middlewares=openwebui-https-redirect"
      - "traefik.http.routers.openwebui-secure.entrypoints=https"
      - "traefik.http.routers.openwebui-secure.rule=Host(`ai.${DOMAIN}`)"
      - "traefik.http.routers.openwebui-secure.tls=true"
      - "traefik.http.routers.openwebui-secure.tls.certresolver=le"
      - "traefik.http.routers.openwebui-secure.service=openwebui"
      - "traefik.http.routers.openwebui-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.openwebui.loadbalancer.server.port=8080"

  it-tools:
    image: ghcr.io/corentinth/it-tools:latest
    container_name: it-tools
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.ittools.entrypoints=http"
      - "traefik.http.routers.ittools.rule=Host(`tools.${DOMAIN}`)"
      - "traefik.http.routers.ittools.middlewares=ittools-https-redirect"
      - "traefik.http.routers.ittools-secure.entrypoints=https"
      - "traefik.http.routers.ittools-secure.rule=Host(`tools.${DOMAIN}`)"
      - "traefik.http.routers.ittools-secure.tls=true"
      - "traefik.http.routers.ittools-secure.tls.certresolver=le"
      - "traefik.http.routers.ittools-secure.service=ittools"
      - "traefik.http.routers.ittools-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.ittools.loadbalancer.server.port=80"

  cyberchef:
    image: ghcr.io/gchq/cyberchef:latest
    container_name: cyberchef
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.cyberchef.entrypoints=http"
      - "traefik.http.routers.cyberchef.rule=Host(`chef.${DOMAIN}`)"
      - "traefik.http.routers.cyberchef.middlewares=cyberchef-https-redirect"
      - "traefik.http.routers.cyberchef-secure.entrypoints=https"
      - "traefik.http.routers.cyberchef-secure.rule=Host(`chef.${DOMAIN}`)"
      - "traefik.http.routers.cyberchef-secure.tls=true"
      - "traefik.http.routers.cyberchef-secure.tls.certresolver=le"
      - "traefik.http.routers.cyberchef-secure.service=cyberchef"
      - "traefik.http.routers.cyberchef-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.cyberchef.loadbalancer.server.port=80"

  netalertx:
    image: jokobsk/netalertx:latest
    container_name: netalertx
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    volumes:
      - ./data/netalertx/config:/app/config
      - ./data/netalertx/db:/app/db
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.netalertx.entrypoints=http"
      - "traefik.http.routers.netalertx.rule=Host(`netalertx.${DOMAIN}`)"
      - "traefik.http.routers.netalertx.middlewares=netalertx-https-redirect"
      - "traefik.http.routers.netalertx-secure.entrypoints=https"
      - "traefik.http.routers.netalertx-secure.rule=Host(`netalertx.${DOMAIN}`)"
      - "traefik.http.routers.netalertx-secure.tls=true"
      - "traefik.http.routers.netalertx-secure.tls.certresolver=le"
      - "traefik.http.routers.netalertx-secure.service=netalertx"
      - "traefik.http.services.netalertx.loadbalancer.server.port=20211"

  netdata:
    image: netdata/netdata:stable
    container_name: netdata
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    cap_add:
      - SYS_PTRACE
    security_opt:
      - apparmor:unconfined
    volumes:
      - ./data/netdata/config:/etc/netdata
      - ./data/netdata/lib:/var/lib/netdata
      - ./data/netdata/cache:/var/cache/netdata
      - /etc/passwd:/host/etc/passwd:ro
      - /etc/group:/host/etc/group:ro
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /etc/os-release:/host/etc/os-release:ro
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.netdata.entrypoints=http"
      - "traefik.http.routers.netdata.rule=Host(`stats.${DOMAIN}`)"
      - "traefik.http.routers.netdata.middlewares=netdata-https-redirect"
      - "traefik.http.routers.netdata-secure.entrypoints=https"
      - "traefik.http.routers.netdata-secure.rule=Host(`stats.${DOMAIN}`)"
      - "traefik.http.routers.netdata-secure.tls=true"
      - "traefik.http.routers.netdata-secure.tls.certresolver=le"
      - "traefik.http.routers.netdata-secure.service=netdata"
      - "traefik.http.routers.netdata-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.netdata.loadbalancer.server.port=19999"

  guacd:
    image: guacamole/guacd:latest
    container_name: guacd
    restart: always
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"

  guacamole:
    image: guacamole/guacamole:latest
    container_name: guacamole
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
      GUACD_HOSTNAME: guacd
    depends_on:
      - guacd
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.guacamole.entrypoints=http"
      - "traefik.http.routers.guacamole.rule=Host(`guac.${DOMAIN}`)"
      - "traefik.http.routers.guacamole.middlewares=guacamole-https-redirect"
      - "traefik.http.routers.guacamole-secure.entrypoints=https"
      - "traefik.http.routers.guacamole-secure.rule=Host(`guac.${DOMAIN}`)"
      - "traefik.http.routers.guacamole-secure.tls=true"
      - "traefik.http.routers.guacamole-secure.tls.certresolver=le"
      - "traefik.http.routers.guacamole-secure.service=guacamole"
      - "traefik.http.services.guacamole.loadbalancer.server.port=8080"

  openspeedtest:
    image: openspeedtest/latest:latest
    container_name: openspeedtest
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.openspeedtest.entrypoints=http"
      - "traefik.http.routers.openspeedtest.rule=Host(`speed.${DOMAIN}`)"
      - "traefik.http.routers.openspeedtest.middlewares=openspeedtest-https-redirect"
      - "traefik.http.routers.openspeedtest-secure.entrypoints=https"
      - "traefik.http.routers.openspeedtest-secure.rule=Host(`speed.${DOMAIN}`)"
      - "traefik.http.routers.openspeedtest-secure.tls=true"
      - "traefik.http.routers.openspeedtest-secure.tls.certresolver=le"
      - "traefik.http.routers.openspeedtest-secure.service=openspeedtest"
      - "traefik.http.routers.openspeedtest-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.openspeedtest.loadbalancer.server.port=3000"

  dozzle:
    image: amir20/dozzle:latest
    container_name: dozzle
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.dozzle.entrypoints=http"
      - "traefik.http.routers.dozzle.rule=Host(`logs.${DOMAIN}`)"
      - "traefik.http.routers.dozzle.middlewares=dozzle-https-redirect"
      - "traefik.http.routers.dozzle-secure.entrypoints=https"
      - "traefik.http.routers.dozzle-secure.rule=Host(`logs.${DOMAIN}`)"
      - "traefik.http.routers.dozzle-secure.tls=true"
      - "traefik.http.routers.dozzle-secure.tls.certresolver=le"
      - "traefik.http.routers.dozzle-secure.service=dozzle"
      - "traefik.http.routers.dozzle-secure.middlewares=authelia@docker" # Comment out if you do not want SSO with Authelia
      - "traefik.http.services.dozzle.loadbalancer.server.port=8080"

  wud:
    image: getwud/wud:latest
    container_name: wud
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data/wud:/store
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.wud.entrypoints=http"
      - "traefik.http.routers.wud.rule=Host(`wud.${DOMAIN}`)"
      - "traefik.http.routers.wud.middlewares=wud-https-redirect"
      - "traefik.http.routers.wud-secure.entrypoints=https"
      - "traefik.http.routers.wud-secure.rule=Host(`wud.${DOMAIN}`)"
      - "traefik.http.routers.wud-secure.tls=true"
      - "traefik.http.routers.wud-secure.tls.certresolver=le"
      - "traefik.http.routers.wud-secure.service=wud"
      - "traefik.http.services.wud.loadbalancer.server.port=3000"

  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: always
    env_file:
      - .env
    environment:
      TZ: ${TZ}
      PUID: 999
      PGID: 999
    volumes:
      - ./data/jellyfin/config:/config
      - ./data/jellyfin/media:/media
    networks:
      - proxy
    logging:
      options:
        max-size: 10m
        max-file: "5"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.jellyfin.entrypoints=http"
      - "traefik.http.routers.jellyfin.rule=Host(`media.${DOMAIN}`)"
      - "traefik.http.routers.jellyfin.middlewares=jellyfin-https-redirect"
      - "traefik.http.routers.jellyfin-secure.entrypoints=https"
      - "traefik.http.routers.jellyfin-secure.rule=Host(`media.${DOMAIN}`)"
      - "traefik.http.routers.jellyfin-secure.tls=true"
      - "traefik.http.routers.jellyfin-secure.tls.certresolver=le"
      - "traefik.http.routers.jellyfin-secure.service=jellyfin"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"

networks:
  proxy:
    external: true

.env-template:

# Copy this file to .env and adjust values for your environment.

TZ=America/New_York
DOMAIN=domain.tld

# Email used for Let's Encrypt registration.
LE_EMAIL=admin@domain.tld

# Traefik dashboard basic auth hash.
# Generate with one of:
#   htpasswd -nbB admin 'CHANGE_ME'
#   htpasswd -nbm admin 'CHANGE_ME'
#
# If you paste directly into .env, keep single $ signs here.
BASIC_AUTH_USERS=admin:$apr1$replace$replacewithhtpasswdhash

# Uncomment and fill only if using ACME DNS challenge with Cloudflare
# CF_API_EMAIL=
# CF_API_KEY=
# CF_DNS_API_TOKEN=

# Uncomment and fill only if using ACME DNS challenge with DNSMadeEasy
# DNSMADEEASY_API_KEY=
# DNSMADEEASY_API_SECRET=

# Uncomment and fill only if using ACME DNS challenge with GoDaddy
# GODADDY_API_KEY=
# GODADDY_API_SECRET=

Watchtower docker-compose.yml (if desired):

services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    env_file:
      - .env
    environment:
      TZ: ${TZ}
      WATCHTOWER_LABEL_ENABLE: "true"
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_INCLUDE_RESTARTING: "true"
      WATCHTOWER_POLL_INTERVAL: "300"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --label-enable --cleanup --interval 300
    logging:
      options:
        max-size: 10m
        max-file: "5"

All the instuctions are in the docker-compose.yml comments on top. Ping me if you have any questions. Hopefully this helps someone.

Thanks

1 Like