While I try to spend a majority of my freetime outdoors, or at least interacting with nature in the form of working on my terrariums and aquariums, occasionally I have to indulge in some infrastructure bikeshedding and reimplementation of existing tools, like any other IT worker.

To that end, I run an increasingly-convoluted homelab setup, though I use the term loosely as it spans both my home and various baremetal server providers (thanks, Hetzner and OVH).

The software itself is a mix of tools from the wonderful Awesome-Selfhosted list and some questionable Go and Bash of my own design.

In front of it all sits Traefik, handling TLS termination, reverse proxying, and authentication (when I can be bothered).

I’ve included the full Docker Compose stack (yes, I’m aware of k8s, don’t @ me) used for this at the end of this post.

[…]

  1## Please email [email protected] with any questions about this file, or setting up any services therein
  2## The actual files are not a monolith like this one; this page is generated as a convenient reference
  3
  4## The following variables should be set in a file named .env in the same directory as docker-compose.yml (alter as needed):
  5# - CLOUDFLARE_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  6# - TIMEZONE=America/Chicago
  7# - UID=1000
  8# - GID=1000
  9
 10services:
 11
 12  ### https://github.com/traefik/traefik
 13  ## Files for basicauth middleware should be generated with the following:
 14  # $ htpasswd -c "${filename}" "${username}"
 15  traefik:
 16    image: traefik:latest
 17    container_name: traefik
 18    restart: unless-stopped
 19    environment:
 20      - "CF_DNS_API_TOKEN=${CLOUDFLARE_API_TOKEN}"
 21      - "TZ=${TIMEZONE:?not set}"
 22    command:
 23      - "--accesslog=true"
 24      - "--accesslog.bufferingsize=100"
 25      - "--accesslog.fields.names.StartUTC=drop"
 26      - "--accesslog.filepath=/var/log/access.log"
 27      - "--api"
 28      - "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
 29      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
 30      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53"
 31      - "--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json"
 32      - "--entrypoints.http-private.address=:1080"
 33      - "--entrypoints.http-private.http.redirections.entrypoint.scheme=https"
 34      - "--entrypoints.http-private.http.redirections.entryPoint.to=https-private"
 35      - "--entrypoints.https-private.address=:1443"
 36      - "--entrypoints.https-private.http3"
 37      - "--entrypoints.https-private.http3.advertisedport=443"
 38      - "--entrypoints.http-public.address=:2080"
 39      - "--entrypoints.http-public.http.redirections.entrypoint.scheme=https"
 40      - "--entrypoints.http-public.http.redirections.entryPoint.to=https-public"
 41      - "--entrypoints.https-public.address=:2443"
 42      - "--entrypoints.https-public.http3"
 43      - "--entrypoints.https-public.http3.advertisedport=443"
 44      - "--log.filePath=/var/log/traefik.log"
 45      - "--log.level=ERROR"
 46      - "--providers.docker=true"
 47      - "--providers.docker.exposedByDefault=false"
 48      - "--providers.docker.network=traefik"
 49      - "--providers.file.directory=/conf"
 50      - "--providers.file.watch=true"
 51      - "--providers.providersThrottleDuration=10s"
 52      - "--serversTransport.insecureSkipVerify=true"
 53    labels:
 54      - "traefik.enable=true"
 55      - "traefik.http.routers.traefik.rule=Host(`dashboard.seedno.de`)"
 56      - "traefik.http.routers.traefik.entrypoints=https-public"
 57      - "traefik.http.routers.traefik.service=api@internal"
 58      - "traefik.http.routers.traefik.tls=true"
 59      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
 60      - "traefik.http.routers.traefik.middlewares=adminauth@file,compress@file,errors@file,secure@file"
 61    ports:
 62      - "${TAILSCALE_IP:?not set}:80:1080"
 63      - "${TAILSCALE_IP:?not set}:443:1443"
 64      - "${TAILSCALE_IP:?not set}:443:1443/udp"
 65      - "${PUBLIC_IP:?not set}:80:2080"
 66      - "${PUBLIC_IP:?not set}:443:2443"
 67      - "${PUBLIC_IP:?not set}:443:2443/udp"
 68    networks:
 69      - traefik
 70      - audio
 71      - code-server
 72      - cyberchef
 73      - errors
 74      - feedcord
 75      - guacamole
 76      - huginn
 77      - naughty
 78      - nginx
 79      - ollama
 80      - open-webui
 81      - query
 82      - random
 83      - recipya
 84      - registry
 85      - stirling-pdf
 86      - thelounge
 87      - trivia
 88      - vaultwarden
 89      - wastebin
 90    volumes:
 91      - type: bind
 92        source: /docker/traefik/certs
 93        target: /certs
 94      - type: bind
 95        source: /docker/traefik/config
 96        target: /conf
 97        read_only: true
 98      - type: bind
 99        source: /docker/traefik/logs
100        target: /var/log
101      - type: bind
102        source: /var/run/docker.sock
103        target: /var/run/docker.sock
104        read_only: true
105
106  ### https://github.com/Seednode/roulette
107  audio:
108    image: oci.seedno.de/seednode/roulette:latest
109    container_name: audio
110    restart: unless-stopped
111    environment:
112      - "ROULETTE_AUDIO=true"
113      - "ROULETTE_DEBUG=true"
114      - "ROULETTE_PREFIX=/random/"
115      - "ROULETTE_RECURSIVE=true"
116      - "ROULETTE_VERBOSE=true"
117      - "TZ=${TIMEZONE:?not set}"
118    command:
119      - "/data"
120    labels:
121      - "traefik.enable=true"
122      - "traefik.http.middlewares.audio.headers.contentSecurityPolicy=default-src 'self'"
123      - "traefik.http.routers.audio.rule=Host(`voice.seedno.de`) && PathPrefix(`/random`)"
124      - "traefik.http.routers.audio.entrypoints=https-public"
125      - "traefik.http.routers.audio.service=audio"
126      - "traefik.http.routers.audio.tls=true"
127      - "traefik.http.routers.audio.tls.certresolver=letsencrypt"
128      - "traefik.http.routers.audio.middlewares=audio,compress@file,errors@file,secure@file"
129      - "traefik.http.services.audio.loadbalancer.server.port=8080"
130    networks:
131      - audio
132    volumes:
133      - type: bind
134        source: /var/www/html/voice.seedno.de
135        target: /data
136        read_only: true
137
138  ### https://github.com/coder/code-server
139  code-server:
140    image: lscr.io/linuxserver/code-server:latest
141    container_name: code-server
142    restart: unless-stopped
143    privileged: true
144    environment:
145      PUID: ${UID:?not set}
146      PGID: ${GID:?not set}
147      SUDO_PASSWORD: ${CODE_SUDO_PASSWORD:?not set}
148      TZ: ${TIMEZONE:?not set}
149      INSTALL_PACKAGES: "fsharp rust-src"
150      DOCKER_MODS: "linuxserver/mods:code-server-awscli|\
151                    linuxserver/mods:code-server-dotnet|\
152                    linuxserver/mods:code-server-extension-arguments|\
153                    linuxserver/mods:code-server-golang|\
154                    linuxserver/mods:code-server-java11|\
155                    linuxserver/mods:code-server-nodejs|\
156                    linuxserver/mods:code-server-php8|\
157                    linuxserver/mods:code-server-powershell|\
158                    linuxserver/mods:code-server-python3|\
159                    linuxserver/mods:code-server-rust|\
160                    linuxserver/mods:code-server-scikit-learn|\
161                    linuxserver/mods:code-server-shellcheck|\
162                    linuxserver/mods:code-server-terraform|\
163                    linuxserver/mods:code-server-zsh|\
164                    linuxserver/mods:universal-package-install"
165      VSCODE_EXTENSION_IDS: "amodio.tsl-problem-matcher|\
166                             bungcip.better-toml|\
167                             coolbear.systemd-unit-file|\
168                             dbaeumer.vscode-eslint|\
169                             esbenp.prettier-vscode|\
170                             golang.go|\
171                             Ionide.Ionide-fsharp|\
172                             mads-hartmann.bash-ide-vscode|\
173                             ms-azuretools.vscode-containers|\
174                             ms-dotnettools.csdevkit|\
175                             ms-python.python|\
176                             ms-vscode.PowerShell|\
177                             muhammad-sammy.csharp|\
178                             rust-lang.rust-analyzer|\
179                             timonwong.shellcheck"
180    labels:
181      - "traefik.enable=true"
182      - "traefik.http.middlewares.code-server.headers.contentSecurityPolicy=default-src 'self'; style-src 'self' 'unsafe-inline' https://*.vscode-cdn.net; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.vscode-cdn.net; img-src 'self' data: https://*.vscode-cdn.net; connect-src 'self' https://open-vsx.org"
183      - "traefik.http.routers.code-server.rule=Host(`code.seedno.de`)"
184      - "traefik.http.routers.code-server.entrypoints=https-public"
185      - "traefik.http.routers.code-server.service=code-server"
186      - "traefik.http.routers.code-server.tls=true"
187      - "traefik.http.routers.code-server.tls.certresolver=letsencrypt"
188      - "traefik.http.routers.code-server.middlewares=adminauth@file,code-server,compress@file,errors@file,secure@file"
189      - "traefik.http.services.code-server.loadbalancer.server.port=8443"
190    networks:
191      - code-server
192    volumes:
193      - type: bind
194        source: /docker/code-server/config
195        target: /config
196      - type: bind
197        source: /home/sinc/bin
198        target: /config/workspace/bin
199      - type: bind
200        source: /home/sinc/code
201        target: /config/workspace/code
202      - type: bind
203        source: /home/sinc/Dropbox/Blog
204        target: /config/workspace/blog
205      - type: bind
206        source: /home/sinc/Dropbox/Documents
207        target: /config/workspace/documents
208      - type: bind
209        source: /home/sinc/Dropbox/KB
210        target: /config/workspace/kb
211      - type: bind
212        source: /home/sinc/trivia
213        target: /config/workspace/trivia
214      - type: bind
215        source: /var/www/html
216        target: /config/workspace/html
217
218  ### https://github.com/gchq/CyberChef
219  cyberchef:
220    image: ghcr.io/gchq/cyberchef:latest
221    container_name: cyberchef
222    restart: unless-stopped
223    labels:
224      - "traefik.enable=true"
225      - "traefik.http.middlewares.cyberchef.headers.contentSecurityPolicy=default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'self' data:; frame-src 'self' data:; worker-src 'self' blob: data:"
226      - "traefik.http.routers.cyberchef.rule=Host(`tools.seedno.de`)"
227      - "traefik.http.routers.cyberchef.entrypoints=https-public"
228      - "traefik.http.routers.cyberchef.service=cyberchef"
229      - "traefik.http.routers.cyberchef.tls=true"
230      - "traefik.http.routers.cyberchef.tls.certresolver=letsencrypt"
231      - "traefik.http.routers.cyberchef.middlewares=compress@file,cyberchef,errors@file,secure@file"
232      - "traefik.http.services.cyberchef.loadbalancer.server.port=80"
233    networks:
234      - cyberchef
235
236  ### https://git.seedno.de/seednode/docker-nginx
237  errors:
238    image: oci.seedno.de/seednode/nginx:latest
239    container_name: errors
240    restart: unless-stopped
241    labels:
242      - "traefik.enable=true"
243      - "traefik.http.routers.errors.entrypoints=https-public"
244      - "traefik.http.services.errors.loadbalancer.server.port=8080"
245    networks:
246      - errors
247    volumes:
248      - type: bind
249        source: /docker/errors/config/nginx.conf
250        target: /etc/nginx/nginx.conf
251        read_only: true
252      - type: bind
253        source: /docker/errors/logs
254        target: /var/log/nginx
255      - type: bind
256        source: /docker/errors/data
257        target: /var/www/html
258        read_only: true
259
260  ### https://github.com/Qolors/FeedCord
261  feedcord:
262    image: qolors/feedcord:latest
263    container_name: feedcord
264    restart: unless-stopped
265    networks:
266      - feedcord
267    volumes:
268      - type: bind
269        source: /docker/feedcord/data/appsettings.json
270        target: /app/config/appsettings.json
271        read_only: true
272
273  ### https://github.com/apache/guacamole-server
274  ## To generate the database schema:
275  # $ docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgresql > /tmp/initdb.sql
276  # $ docker cp /tmp/initdb.sql guacamole-db:/tmp/initdb.sql
277  # $ docker exec -it guacamole-db /bin/sh -c "cat /tmp/initdb.sql | psql --dbname=${GUACAMOLE_DATABASE_NAME} --user=${GUACAMOLE_DATABASE_USER} -f -"
278  ## When configuring a connection, set the following for guacd parameters:
279  # - Hostname: guacd
280  # - Port: 4822
281  # - Encryption: None (unencrypted)
282  ## RDP sessions should have a security mode of "NLA (Network Level Authentication)"
283  guacamole:
284    image: guacamole/guacamole:latest
285    container_name: guacamole
286    restart: unless-stopped
287    environment:
288      - "GUACD_HOSTNAME=guacd"
289      - "POSTGRES_HOSTNAME=guacamole-db"
290      - "POSTGRES_DATABASE=${GUACAMOLE_DATABASE_NAME:?not set}"
291      - "POSTGRES_USER=${GUACAMOLE_DATABASE_USER:?not set}"
292      - "POSTGRES_PASSWORD=${GUACAMOLE_DATABASE_PASS:?not set}"
293    labels:
294      - "traefik.enable=true"
295      - "traefik.http.middlewares.guacamole.headers.contentSecurityPolicy=default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; img-src 'self' data:"
296      - "traefik.http.routers.guacamole.rule=Host(`guacamole.seedno.de`) && PathPrefix(`/guacamole/`)"
297      - "traefik.http.routers.guacamole.entrypoints=https-public"
298      - "traefik.http.routers.guacamole.service=guacamole"
299      - "traefik.http.routers.guacamole.tls=true"
300      - "traefik.http.routers.guacamole.tls.certresolver=letsencrypt"
301      - "traefik.http.routers.guacamole.middlewares=compress@file,errors@file,secure@file,guacamole"
302      - "traefik.http.services.guacamole.loadbalancer.server.port=8080"
303    networks:
304      - guacamole
305
306  guacamole-db:
307    image: postgres:16-alpine
308    container_name: guacamole-db
309    restart: unless-stopped
310    environment:
311      - "POSTGRES_DB=${GUACAMOLE_DATABASE_NAME:?not set}"
312      - "POSTGRES_USER=${GUACAMOLE_DATABASE_USER:?not set}"
313      - "POSTGRES_PASSWORD=${GUACAMOLE_DATABASE_PASS:?not set}"
314    networks:
315      - guacamole
316    volumes:
317      - type: bind
318        source: /docker/guacamole/database
319        target: /var/lib/postgresql/data
320
321  guacd:
322    image: guacamole/guacd
323    container_name: guacd
324    restart: unless-stopped
325    networks:
326      - guacamole
327
328  ### https://github.com/huginn/huginn
329  huginn-web:
330    image: ghcr.io/huginn/huginn-single-process:latest
331    container_name: huginn-web
332    restart: unless-stopped
333    depends_on:
334      - huginn-db
335    environment:
336      - "APP_SECRET_TOKEN=${HUGINN_APP_SECRET:?not set}"
337      - "DATABASE_ADAPTER=postgresql"
338      - "DATABASE_ENCODING=utf8"
339      - "DATABASE_NAME=${HUGINN_DATABASE_NAME:?not set}"
340      - "DATABASE_USERNAME=${HUGINN_DATABASE_USER:?not set}"
341      - "DATABASE_PASSWORD=${HUGINN_DATABASE_PASS:?not set}"
342      - "DATABASE_POOL=20"
343      - "DATABASE_RECONNECT=true"
344      - "DOMAIN=odin.seedno.de:443"
345      - "FORCE_SSL=true"
346      - "INVITATION_CODE=${HUGINN_INVITE_CODE:?not set}"
347      - "PORT=3000"
348      - "POSTGRES_PORT_5432_TCP_ADDR=huginn-db"
349      - "POSTGRES_PORT_5432_TCP_PORT=5432"
350      - "RAILS_ENV=production"
351      - "REQUIRE_CONFIRMED_EMAIL=false"
352      - "SKIP_INVITATION_CODE=false"
353      - "TIMEZONE=${TIMEZONE:?not set}"
354    labels:
355      - "traefik.enable=true"
356      - "traefik.http.routers.huginn.rule=Host(`odin.seedno.de`)"
357      - "traefik.http.routers.huginn.entrypoints=https-public"
358      - "traefik.http.routers.huginn.service=huginn"
359      - "traefik.http.routers.huginn.tls=true"
360      - "traefik.http.routers.huginn.tls.certresolver=letsencrypt"
361      - "traefik.http.routers.huginn.middlewares=compress@file,errors@file,secure@file"
362      - "traefik.http.services.huginn.loadbalancer.server.port=3000"
363    networks:
364      - huginn
365      - huginn-db
366
367  huginn-threaded:
368    image: ghcr.io/huginn/huginn-single-process:latest
369    container_name: huginn-threaded
370    restart: unless-stopped
371    depends_on:
372      - huginn-db
373      - huginn-web
374    environment:
375      - "APP_SECRET_TOKEN=${HUGINN_APP_SECRET:?not set}"
376      - "DATABASE_ADAPTER=postgresql"
377      - "DATABASE_ENCODING=utf8"
378      - "DATABASE_NAME=${HUGINN_DATABASE_NAME:?not set}"
379      - "DATABASE_USERNAME=${HUGINN_DATABASE_USER:?not set}"
380      - "DATABASE_PASSWORD=${HUGINN_DATABASE_PASS:?not set}"
381      - "DATABASE_POOL=20"
382      - "DATABASE_RECONNECT=true"
383      - "DOMAIN=odin.seedno.de:443"
384      - "FORCE_SSL=true"
385      - "INVITATION_CODE=${HUGINN_INVITE_CODE:?not set}"
386      - "PORT=3000"
387      - "POSTGRES_PORT_5432_TCP_ADDR=huginn-db"
388      - "POSTGRES_PORT_5432_TCP_PORT=5432"
389      - "RAILS_ENV=production"
390      - "REQUIRE_CONFIRMED_EMAIL=false"
391      - "SKIP_INVITATION_CODE=false"
392      - "TIMEZONE=${TIMEZONE:?not set}"
393    command: /scripts/init bin/threaded.rb
394    networks:
395      - huginn
396      - huginn-db
397
398  ### https://github.com/postgres/postgres
399  huginn-db:
400    image: postgres:16-alpine
401    container_name: huginn-db
402    restart: unless-stopped
403    environment:
404      - "POSTGRES_DB=${HUGINN_DATABASE_NAME:?not set}"
405      - "POSTGRES_USER=${HUGINN_DATABASE_USER:?not set}"
406      - "POSTGRES_PASSWORD=${HUGINN_DATABASE_PASS:?not set}"
407    networks:
408      - huginn-db
409    volumes:
410      - type: bind
411        source: /docker/huginn/database
412        target: /var/lib/postgresql/data
413
414  ### https://github.com/postgres/postgres
415  learning-db:
416    image: postgres:16-alpine
417    container_name: learning-db
418    restart: unless-stopped
419    environment:
420      - "POSTGRES_DB=${LEARNING_DATABASE_NAME:?not set}"
421      - "POSTGRES_USER=${LEARNING_DATABASE_USER:?not set}"
422      - "POSTGRES_PASSWORD=${LEARNING_DATABASE_PASS:?not set}"
423    networks:
424      - learning-db
425    volumes:
426      - type: bind
427        source: /docker/learning/database
428        target: /var/lib/postgresql/data
429
430  ### https://github.com/Seednode/docker-nginx
431  naughty:
432    image: oci.seedno.de/seednode/nginx:latest
433    container_name: naughty
434    restart: unless-stopped
435    labels:
436      - "traefik.enable=true"
437      - "traefik.http.routers.naughty.rule=PathPrefix(`/{path:.*(\\.env|\\.aws|xmlrpc|wp-admin|wp-login).*}`)"
438      - "traefik.http.routers.naughty.priority=25400"
439      - "traefik.http.routers.naughty.entrypoints=https-public"
440      - "traefik.http.routers.naughty.service=naughty"
441      - "traefik.http.routers.naughty.tls=true"
442      - "traefik.http.routers.naughty.tls.certresolver=letsencrypt"
443      - "traefik.http.routers.naughty.middlewares=compress@file,errors@file,secure@file"
444      - "traefik.http.services.naughty.loadbalancer.server.port=8080"
445    networks:
446      - naughty
447    volumes:
448      - type: bind
449        source: /docker/naughty/config/nginx.conf
450        target: /etc/nginx/nginx.conf
451        read_only: true
452      - type: bind
453        source: /docker/naughty/logs
454        target: /var/log/nginx
455      - type: bind
456        source: /docker/naughty/data
457        target: /var/www/html
458        read_only: true
459
460  ### https://github.com/Seednode/docker-nginx
461  ## All my sites are configured via the file provider
462  ## An example is available here:
463  ## https://cdn.seedno.de/txt/access.seedno.de.yaml
464  nginx:
465    image: oci.seedno.de/seednode/nginx:latest
466    container_name: nginx
467    restart: unless-stopped
468    depends_on:
469      - nginx-php-fpm
470    networks:
471      - nginx
472      - nginx-php
473    volumes:
474      - type: bind
475        source: /docker/nginx/config
476        target: /etc/nginx
477        read_only: true
478      - type: bind
479        source: /docker/nginx/logs
480        target: /var/log/nginx
481      - type: bind
482        source: /var/www/html
483        target: /var/www/html
484        read_only: true
485
486  nginx-php-fpm:
487    image: php:8-fpm-alpine
488    container_name: nginx-php-fpm
489    restart: unless-stopped
490    networks:
491      - nginx-php
492    volumes:
493      - type: bind
494        source: /var/www/html
495        target: /var/www/html
496        read_only: true
497
498  ### https://github.com/ollama/ollama
499  ollama:
500    image: ollama/ollama:latest
501    container_name: ollama
502    restart: unless-stopped
503    labels:
504      - "traefik.enable=true"
505      - "traefik.http.routers.ollama.rule=Host(`ollama.seedno.de`)"
506      - "traefik.http.routers.ollama.entrypoints=https-private"
507      - "traefik.http.routers.ollama.service=ollama"
508      - "traefik.http.routers.ollama.tls=true"
509      - "traefik.http.routers.ollama.tls.certresolver=letsencrypt"
510      - "traefik.http.routers.ollama.middlewares=allowlist@file,compress@file,errors@file,secure@file"
511      - "traefik.http.services.ollama.loadbalancer.server.port=11434"
512    networks:
513      - ollama
514    volumes:
515      - type: bind
516        source: /docker/ollama/data
517        target: /root/.ollama
518
519  ### https://github.com/open-webui/open-webui
520  open-webui:
521    image: ghcr.io/open-webui/open-webui:main
522    container_name: open-webui
523    restart: unless-stopped
524    environment:
525      - "[email protected]"
526      - "CORS_ALLOW_ORIGIN=https://*.seedno.de"
527      - "ENABLE_FORWARD_USER_INFO_HEADERS=True"
528      - "ENABLE_OPENAI_API=False"
529      - "ENABLE_PERSISTENT_CONFIG=False"
530      - "ENABLE_SIGNUP=False"
531      - "OFFLINE_MODE=True"
532      - "OLLAMA_BASE_URL=http://ollama:11434"
533      - "WEBUI_AUTH=False"
534      - "WEBUI_NAME=Seedno.deAI"
535      - "WEBUI_URL=https://ai.seedno.de"
536    labels:
537      - "traefik.enable=true"
538      - "traefik.http.routers.open-webui.rule=Host(`ai.seedno.de`)"
539      - "traefik.http.routers.open-webui.entrypoints=https-private"
540      - "traefik.http.routers.open-webui.service=open-webui"
541      - "traefik.http.routers.open-webui.tls=true"
542      - "traefik.http.routers.open-webui.tls.certresolver=letsencrypt"
543      - "traefik.http.routers.open-webui.middlewares=allowlist@file,compress@file,errors@file,secure@file"
544      - "traefik.http.services.open-webui.loadbalancer.server.port=8080"
545    networks:
546      - open-webui
547      - ollama
548    volumes:
549      - type: bind
550        source: /docker/open-webui/data
551        target: /app/backend/data
552
553  ### https://github.com/Seednode/query
554  query:
555    image: oci.seedno.de/seednode/query:latest
556    container_name: query
557    restart: unless-stopped
558    environment:
559      - "QUERY_ALL=true"
560      - "QUERY_DNS_RESOLVER=1.1.1.1:53"
561      - "QUERY_MAX_DICE_ROLLS=1024"
562      - "QUERY_MAX_DICE_SIDES=1024"
563      - "QUERY_QR_SIZE=512"
564      - "QUERY_VERBOSE=true"
565      - "TZ=${TIMEZONE:?not set}"
566    labels:
567      - "traefik.enable=true"
568      - "traefik.http.middlewares.query.headers.contentSecurityPolicy=default-src 'self'"
569      - "traefik.http.routers.query.rule=Host(`q.seedno.de`)"
570      - "traefik.http.routers.query.entrypoints=https-public"
571      - "traefik.http.routers.query.service=query"
572      - "traefik.http.routers.query.tls=true"
573      - "traefik.http.routers.query.tls.certresolver=letsencrypt"
574      - "traefik.http.routers.query.middlewares=compress@file,errors@file,query,ratelimit@file,secure@file"
575      - "traefik.http.services.query.loadbalancer.server.port=8080"
576    networks:
577      - query
578
579  ### https://github.com/Seednode/roulette
580  random:
581    image: oci.seedno.de/seednode/roulette:latest
582    container_name: random
583    restart: unless-stopped
584    environment:
585      - "ROULETTE_ALL=true"
586      - "ROULETTE_DEBUG=true"
587      - "ROULETTE_PREFIX=/random/"
588      - "ROULETTE_RECURSIVE=true"
589      - "ROULETTE_VERBOSE=true"
590      - "TZ=${TIMEZONE:?not set}"
591    command:
592      - "/data"
593    labels:
594      - "traefik.enable=true"
595      - "traefik.http.middlewares.random.headers.contentSecurityPolicy=default-src 'self'"
596      - "traefik.http.routers.random.rule=Host(`cdn.seedno.de`) && PathPrefix(`/random`)"
597      - "traefik.http.routers.random.entrypoints=https-public"
598      - "traefik.http.routers.random.service=random"
599      - "traefik.http.routers.random.tls=true"
600      - "traefik.http.routers.random.tls.certresolver=letsencrypt"
601      - "traefik.http.routers.random.middlewares=compress@file,errors@file,random,secure@file"
602      - "traefik.http.services.random.loadbalancer.server.port=8080"
603    networks:
604      - random
605    volumes:
606      - type: bind
607        source: /var/www/html/cdn.seedno.de
608        target: /data
609        read_only: true
610
611  ### https://github.com/reaper47/recipya
612  recipya:
613    image: reaper99/recipya:nightly
614    container_name: recipya
615    restart: unless-stopped
616    environment:
617      - "RECIPYA_SERVER_IS_PROD=true"
618      - "RECIPYA_SERVER_NO_SIGNUPS=true"
619      - "RECIPYA_SERVER_PORT=8078"
620      - "RECIPYA_SERVER_URL=https://recipes.seedno.de"
621    labels:
622      - "traefik.enable=true"
623      - "traefik.http.routers.recipya.rule=Host(`recipes.seedno.de`)"
624      - "traefik.http.routers.recipya.entrypoints=https-public"
625      - "traefik.http.routers.recipya.service=recipya"
626      - "traefik.http.routers.recipya.tls=true"
627      - "traefik.http.routers.recipya.tls.certresolver=letsencrypt"
628      - "traefik.http.routers.recipya.middlewares=compress@file,errors@file,secure@file"
629      - "traefik.http.services.recipya.loadbalancer.server.port=8078"
630    networks:
631      - recipya
632    volumes:
633      - type: bind
634        source: /docker/recipya/data
635        target: /home/recipya/.config/Recipya
636
637  ### https://hub.docker.com/_/registry
638  registry:
639    image: registry:3
640    container_name: registry
641    restart: unless-stopped
642    environment:
643      - "REGISTRY_HTTP_ADDR=0.0.0.0:5000"
644      - "REGISTRY_HTTP_SECRET=${REGISTRY_SECRET:?not set}"
645    labels:
646      - "traefik.enable=true"
647      - "traefik.http.routers.registry.rule=Host(`oci.seedno.de`)"
648      - "traefik.http.routers.registry.entrypoints=https-private"
649      - "traefik.http.routers.registry.service=registry"
650      - "traefik.http.routers.registry.tls=true"
651      - "traefik.http.routers.registry.tls.certresolver=letsencrypt"
652      - "traefik.http.routers.registry.middlewares=allowlist@file,compress@file,errors@file,secure@file"
653      - "traefik.http.services.registry.loadbalancer.server.port=5000"
654    networks:
655      - registry
656    volumes:
657      - type: bind
658        source: /docker/registry/config/config.yml
659        target: /etc/docker/registry/config.yml
660        read_only: true
661      - type: bind
662        source: /docker/registry/data
663        target: /var/lib/registry
664
665  ### https://github.com/Stirling-Tools/Stirling-PDF
666  stirling-pdf:
667    image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
668    container_name: stirling-pdf
669    restart: unless-stopped
670    environment:
671      - "DOCKER_ENABLE_SECURITY=false"
672      - "LANGS=en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
673      - "METRICS_ENABLED=false"
674      - "SECURITY_ENABLELOGIN=false"
675      - "SHOW_SURVEY=false"
676      - "SYSTEM_DEFAULTLOCALE=en-US"
677      - "SYSTEM_GOOGLEVISIBILITY=false"
678      - "SYSTEM_MAXFILESIZE=100"
679      - "UI_APPNAME=PDF Converter"
680      - "UI_APPNAMENAVBAR=PDF Converter"
681      - "UI_HOMEDESCRIPTION=Seednode's PDF Converter"
682    labels:
683      - "traefik.enable=true"
684      - "traefik.http.routers.stirling-pdf.rule=Host(`pdf.seedno.de`)"
685      - "traefik.http.routers.stirling-pdf.entrypoints=https-public"
686      - "traefik.http.routers.stirling-pdf.service=stirling-pdf"
687      - "traefik.http.routers.stirling-pdf.tls=true"
688      - "traefik.http.routers.stirling-pdf.tls.certresolver=letsencrypt"
689      - "traefik.http.routers.stirling-pdf.middlewares=adminauth@file,compress@file,secure@file"
690      - "traefik.http.services.stirling-pdf.loadbalancer.server.port=8080"
691    networks:
692      - stirling-pdf
693    volumes:
694      - type: bind
695        source: /docker/stirling-pdf/config
696        target: /configs
697      - type: bind
698        source: /docker/stirling-pdf/logs
699        target: /logs
700      - type: bind
701        source: /docker/stirling-pdf/data
702        target: /docker/tessdata
703
704  ### https://github.com/thelounge/thelounge
705  thelounge:
706    image: lscr.io/linuxserver/thelounge:latest
707    container_name: thelounge
708    restart: unless-stopped
709    environment:
710      - "PUID=${UID:?not set}"
711      - "PGID=${GID:?not set}"
712      - "TZ=${TIMEZONE:?not set}"
713    labels:
714      - "traefik.enable=true"
715      - "traefik.http.middlewares.thelounge.headers.contentSecurityPolicy=default-src 'self'; style-src 'self' 'unsafe-inline'"
716      - "traefik.http.routers.thelounge.rule=Host(`chat.seedno.de`)"
717      - "traefik.http.routers.thelounge.entrypoints=https-public"
718      - "traefik.http.routers.thelounge.service=thelounge"
719      - "traefik.http.routers.thelounge.tls=true"
720      - "traefik.http.routers.thelounge.tls.certresolver=letsencrypt"
721      - "traefik.http.routers.thelounge.middlewares=compress@file,errors@file,secure@file,thelounge"
722      - "traefik.http.services.thelounge.loadbalancer.server.port=9000"
723    networks:
724      - thelounge
725    volumes:
726      - type: bind
727        source: /docker/thelounge/config
728        target: /config
729
730  ### https://github.com/Seednode/trivia
731  trivia:
732    image: oci.seedno.de/seednode/trivia:latest
733    container_name: trivia
734    restart: unless-stopped
735    environment:
736      - "TRIVIA_COLORS=/data/colors.txt"
737      - "TRIVIA_HTML=true"
738      - "TRIVIA_RECURSIVE=true"
739      - "TRIVIA_RELOAD=true"
740      - "TRIVIA_RELOAD_INTERVAL=30m"
741      - "TRIVIA_VERBOSE=true"
742      - "TZ=${TIMEZONE:?not set}"
743    command:
744      - "/data/trivial-pursuit"
745    labels:
746      - "traefik.enable=true"
747      - "traefik.mode=public"
748      - "traefik.http.routers.trivia.rule=Host(`trivia.seedno.de`)"
749      - "traefik.http.routers.trivia.entrypoints=https-public"
750      - "traefik.http.routers.trivia.service=trivia"
751      - "traefik.http.routers.trivia.tls=true"
752      - "traefik.http.routers.trivia.tls.certresolver=letsencrypt"
753      - "traefik.http.routers.trivia.middlewares=compress@file,errors@file,secure@file"
754      - "traefik.http.services.trivia.loadbalancer.server.port=8080"
755    networks:
756      - trivia
757    volumes:
758      - type: bind
759        source: /home/sinc/trivia
760        target: /data
761        read_only: true
762
763  ### https://github.com/dani-garcia/vaultwarden
764  vaultwarden:
765    image: vaultwarden/server:alpine
766    container_name: vaultwarden
767    restart: unless-stopped
768    depends_on:
769      - vaultwarden-db
770    environment:
771      - "DATABASE_URL=postgresql://${VAULTWARDEN_DATABASE_USER:?not set}:${VAULTWARDEN_DATABASE_PASS:?not set}@vaultwarden-db:5432/${VAULTWARDEN_DATABASE_NAME:?not set}"
772      - "DOMAIN=https://vault.seedno.de"
773      - "WEBSOCKET_ENABLED=false"
774      - "SIGNUPS_ALLOWED=true"
775      - "INVITATIONS_ALLOWED=false"
776      - "SHOW_PASSWORD_HINT=false"
777      - "TRASH_AUTO_DELETE_DAYS=30"
778      - "LOG_FILE=/data/vaultwarden.log"
779      - "ROCKET_PORT=80"
780    labels:
781      - "traefik.enable=true"
782      - "traefik.http.middlewares.vaultwarden.headers.contentSecurityPolicy=default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; img-src 'self' data:"
783      - "traefik.http.routers.vaultwarden.rule=Host(`vault.seedno.de`)"
784      - "traefik.http.routers.vaultwarden.entrypoints=https-private"
785      - "traefik.http.routers.vaultwarden.service=vaultwarden"
786      - "traefik.http.routers.vaultwarden.tls=true"
787      - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
788      - "traefik.http.routers.vaultwarden.middlewares=compress@file,errors@file,secure@file,vaultwarden"
789      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
790    networks:
791      - vaultwarden
792      - vaultwarden-db
793    volumes:
794      - type: bind
795        source: /docker/vaultwarden/data
796        target: /data
797
798  vaultwarden-db:
799    image: postgres:16-alpine
800    container_name: vaultwarden-db
801    restart: unless-stopped
802    environment:
803      - "POSTGRES_DB=${VAULTWARDEN_DATABASE_NAME:?not set}"
804      - "POSTGRES_USER=${VAULTWARDEN_DATABASE_USER:?not set}"
805      - "POSTGRES_PASSWORD=${VAULTWARDEN_DATABASE_PASS:?not set}"
806    networks:
807      - vaultwarden-db
808    volumes:
809      - type: bind
810        source: /docker/vaultwarden/database
811        target: /var/lib/postgresql/data
812
813  ### https://github.com/matze/wastebin
814  wastebin:
815    image: quxfoo/wastebin:latest
816    container_name: wastebin
817    restart: unless-stopped
818    environment:
819      - "WASTEBIN_BASE_URL=https://paste.seedno.de"
820      - "WASTEBIN_DATABASE_PATH=/data/state.db"
821      - "WASTEBIN_MAX_PASTE_EXPIRATION=2678400"
822      - "WASTEBIN_PASSWORD_SALT=${WASTEBIN_PASSWORD_SALT:?not set}"
823      - "WASTEBIN_SIGNING_KEY=${WASTEBIN_SIGNING_KEY:?not set}"
824      - "WASTEBIN_THEME=solarized"
825      - "WASTEBIN_TITLE=Pastebin"
826    labels:
827      - "traefik.enable=true"
828      - "traefik.http.middlewares.wastebin.headers.contentSecurityPolicy=default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; img-src 'self' data:"
829      - "traefik.http.routers.wastebin.rule=Host(`paste.seedno.de`)"
830      - "traefik.http.routers.wastebin.entrypoints=https-public"
831      - "traefik.http.routers.wastebin.service=wastebin"
832      - "traefik.http.routers.wastebin.tls=true"
833      - "traefik.http.routers.wastebin.tls.certresolver=letsencrypt"
834      - "traefik.http.routers.wastebin.middlewares=compress@file,errors@file,secure@file,wastebin"
835      - "traefik.http.services.wastebin.loadbalancer.server.port=8088"
836    networks:
837      - wastebin
838    volumes:
839      - type: bind
840        source: /docker/wastebin/data
841        target: /data
842
843networks:
844  traefik:
845    name: traefik
846    driver: bridge
847    driver_opts:
848      com.docker.network.bridge.name: traefik
849  audio:
850    name: audio
851    driver: bridge
852    driver_opts:
853      com.docker.network.bridge.name: audio
854    internal: true
855  code-server:
856    name: code-server
857    driver: bridge
858    driver_opts:
859      com.docker.network.bridge.name: code-server
860  cyberchef:
861    name: cyberchef
862    driver: bridge
863    driver_opts:
864      com.docker.network.bridge.name: cyberchef
865    internal: true
866  errors:
867    name: errors
868    driver: bridge
869    driver_opts:
870      com.docker.network.bridge.name: errors
871    internal: true
872  feedcord:
873    name: feedcord
874    driver: bridge
875    driver_opts:
876      com.docker.network.bridge.name: feedcord
877  guacamole:
878    name: guacamole
879    driver: bridge
880    driver_opts:
881      com.docker.network.bridge.name: guacamole
882  huginn:
883    name: huginn
884    driver: bridge
885    driver_opts:
886      com.docker.network.bridge.name: huginn
887  huginn-db:
888    name: huginn-db
889    driver: bridge
890    driver_opts:
891      com.docker.network.bridge.name: huginn-db
892  learning-db:
893    name: learning-db
894    driver: bridge
895    driver_opts:
896      com.docker.network.bridge.name: learning-db
897    internal: true
898  naughty:
899    name: naughty
900    driver: bridge
901    driver_opts:
902      com.docker.network.bridge.name: naughty
903    internal: true
904  nginx:
905    name: nginx
906    driver: bridge
907    driver_opts:
908      com.docker.network.bridge.name: nginx
909  nginx-php:
910    name: nginx-php
911    driver: bridge
912    driver_opts:
913      com.docker.network.bridge.name: nginx-php
914    internal: true
915  ollama:
916    name: ollama
917    driver: bridge
918    driver_opts:
919      com.docker.network.bridge.name: ollama
920  open-webui:
921    name: open-webui
922    driver: bridge
923    driver_opts:
924      com.docker.network.bridge.name: open-webui
925  query:
926    name: query
927    driver: bridge
928    driver_opts:
929      com.docker.network.bridge.name: query
930  random:
931    name: random
932    driver: bridge
933    driver_opts:
934      com.docker.network.bridge.name: random
935    internal: true
936  recipya:
937    name: recipya
938    driver: bridge
939    driver_opts:
940      com.docker.network.bridge.name: recipya
941  registry:
942    name: registry
943    driver: bridge
944    driver_opts:
945      com.docker.network.bridge.name: registry
946    internal: true
947  stirling-pdf:
948    name: stirling-pdf
949    driver: bridge
950    driver_opts:
951      com.docker.network.bridge.name: stirling-pdf
952  thelounge:
953    name: thelounge
954    driver: bridge
955    driver_opts:
956      com.docker.network.bridge.name: thelounge
957  trivia:
958    name: trivia
959    driver: bridge
960    driver_opts:
961      com.docker.network.bridge.name: trivia
962    internal: true
963  vaultwarden:
964    name: vaultwarden
965    driver: bridge
966    driver_opts:
967      com.docker.network.bridge.name: vaultwarden
968  vaultwarden-db:
969    name: vaultwarden-db
970    driver: bridge
971    driver_opts:
972      com.docker.network.bridge.name: vaultwarden-db
973    internal: true
974  wastebin:
975    name: wastebin
976    driver: bridge
977    driver_opts:
978      com.docker.network.bridge.name: wastebin
979    internal: true