The Utilities Stack
I am sharing this as I believe this will help a great many people understand a few things
Here is my base stack - it also includes Termix. It includes a number of utility services that a lot of people will find handy to say the least.
It is not rocket science it is very simple stuff.
A few notes:
- I have left the homepage labels in to help with your understanding
- I have left the subdomains in to help with your understanding. You will need to create your cname records within your DNS managment interface.
- Watchtower will not update your images for each service in this configuration. It is designed this way. It will notify you and you will then need to refer to the release notification for each service to ensure it does not include breaking changes. Yes, I have made this mistake more than once so I have stopped auto updates.
- Watchtower. Yes this is not the default image. This one is actively maintained, and should be docker ready. Refer to the crap that went down when docker updated its repository and broke a good many servers - its a post.
- The app-net network. Yes its external and created via script on configuration of the baden.braedach.com LXC application server. You can change this to your hearts desire just modify the network. Termix is in its own sub network. Please refer to the appropriate documentation for your preferred container system.
The Code
As per below.
# Utilities stack for Proxmox LXC
# References are included in the individual service sections below
# CNAME records need to be pointed at the proxy not the actual container
# Remember all healthchecks are performed internally within the container, so use localhost and the internal port
# This is a podman compose file, but should work with docker-compose as well. The only difference is the socket path for watchtower.
# This is a podman compose file, the U flag is used to ensure that the volume is created with the correct permissions.
#
# Notes:
# - Considerable work has been done on the healthchecks - dont change
# - termix host port is 6666 (container 8080) - 8080 was already in use by omni-tools
# - termix is attached to app-net (proxy reachable) AND termix-net (to reach guacd)
# - guacd is on termix-net only and is NOT published to the host - only termix needs it
# - before first start, create the bind mount dir: mkdir -p /srv/termix/data
# - termix healthcheck is a TCP connect to localhost:8080 - verify after first boot (see warning below)
#
# Changelog:
# - 2.6.1 (2026-06-20): added healthcheck for termix (TCP connect to localhost:8080)
# Added watchtower monitor-only mode (report updates, do not apply)
# - 2.6.0 (2026-06-10): added termix + guacd (self-hosted SSH/RDP/VNC/Telnet management, Termius alternative)
# - 2.5.0 (2026-05-10): prior baseline
#
# Last updated: 2026-07-10
# Version: 2.6.2
name: utilities-stack
services:
bentopdf:
# https://hub.docker.com/r/bentopdf/bentopdf-simple
image: bentopdf/bentopdf:latest
container_name: bentopdf
hostname: bentopdf
restart: always
ports:
- "8990:8080"
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=bentopdf
- homepage.group=IT Tools
- homepage.name=BentoPDF Service
- homepage.href=https://bentopdf.baden.braedach.com
- com.centurylinklabs.watchtower.enable=true
environment:
- TZ=Australia/Perth
volumes:
# The U flag is used to ensure that the volume is created with the correct permissions for BentoPDF to write to it.
- bentopdf-config:/config:U
- bentopdf-data:/data:U
- bentopdf-logs:/var/log/bentopdf:U
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
healthcheck:
test: wget -q -O - http://localhost:8080/api/v1/healthcheck
interval: 60s
retries: 5
start_period: 60s
timeout: 10s
cap_add:
- SYS_TIME
security_opt:
- no-new-privileges:true
networks:
- app-net
it-tools:
# https://github.com/CorentinTh/it-tools
image: ghcr.io/corentinth/it-tools:latest
container_name: it-tools
hostname: it-tools
restart: always
ports:
- "8880:80"
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=it-tools
- homepage.group=IT Tools
- homepage.name=IT Tools
- homepage.href=https://it-tools.baden.braedach.com
- com.centurylinklabs.watchtower.enable=true
environment:
- TZ=Australia/Perth
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
healthcheck:
test: curl -f -s http://localhost:80/health
interval: 60s
retries: 5
start_period: 60s
timeout: 10s
cap_add:
- SYS_TIME
security_opt:
- no-new-privileges:true
networks:
- app-net
net-tools:
# https://github.com/Lissy93/networking-toolbox
image: lissy93/networking-toolbox:latest
container_name: net-tools
hostname: net-tools
restart: always
ports:
- "8888:8888"
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=net-tools
- homepage.group=IT Tools
- homepage.name=Network Tools
- homepage.href=https://nettools.baden.braedach.com
- com.centurylinklabs.watchtower.enable=true
environment:
- TZ=Australia/Perth
- NODE_ENV=production
- PORT=8888
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
healthcheck:
test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:8888/', r => process.exit(r.statusCode < 400 ? 0 : 1)).on('error', () => process.exit(1))\""]
interval: 60s
timeout: 10s
retries: 5
start_period: 60s
cap_add:
- SYS_TIME
security_opt:
- no-new-privileges:true
networks:
- app-net
omni-tools:
# https://github.com/iib0011/omni-tools#self-hostrun
image: iib0011/omni-tools:latest
container_name: omni-tools
hostname: omni-tools
restart: always
ports:
- 8080:80
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=omni-tools
- homepage.group=IT Tools
- homepage.name=Omni-Tools
- homepage.href=https://omnitools.baden.braedach.com
- com.centurylinklabs.watchtower.enable=true
environment:
- TZ=Australia/Perth
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
healthcheck:
test: curl -f -s http://localhost:80/health
interval: 60s
retries: 5
start_period: 60s
timeout: 10s
security_opt:
- no-new-privileges:true
networks:
- app-net
freshrss:
# https://github.com/FreshRSS/FreshRSS
image: freshrss/freshrss:latest
container_name: freshrss
hostname: freshrss
restart: always
ports:
- 8100:80
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=freshrss
- homepage.group=Family Services
- homepage.name=FreshRSS
- homepage.href=https://freshrss.baden.braedach.com
- com.centurylinklabs.watchtower.enable=true
environment:
- TZ=Australia/Perth
- CRON_MIN=*/15
- BASE_URL=http://freshrss.baden.braedach.com
- ADMIN_EMAIL={YOUR ADMIN EMAIL ADDRESS}
- ADMIN_PASSWORD={YOUR ADMIN PASSWORD}
# Admin API needs be be added after first login.
- ADMIN_API_PASSWORD={PULL THIS FROM THE LOGS}
volumes:
- /srv/freshrss/data:/var/www/FreshRSS/data:U
- /srv/freshrss/extensions:/var/www/FreshRSS/extensions:U
- /srv/freshrss/logs:/var/www/FreshRSS/data/freshrss/logs:U
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
healthcheck:
test: ["CMD", "cli/health.php"]
interval: 60s
timeout: 10s
retries: 5
start_period: 30s
security_opt:
- no-new-privileges:true
networks:
- app-net
termix:
# https://github.com/Termix-SSH/Termix
# Self-hosted SSH/RDP/VNC/Telnet management (Termius alternative). Remote desktop via guacd.
image: ghcr.io/lukegus/termix:latest
container_name: termix
hostname: termix
restart: always
ports:
- 6666:8080
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=termix
- homepage.group=IT Tools
- homepage.name=Termix
- homepage.href=https://termix.baden.braedach.com
- com.centurylinklabs.watchtower.enable=true
environment:
- TZ=Australia/Perth
- PORT=8080
volumes:
- /srv/termix/data:/app/data:U
- /etc/localtime:/etc/localtime:ro
depends_on:
- guacd
healthcheck:
# TCP connect check - no documented HTTP health endpoint, and Termix may redirect HTTP->HTTPS
test: ["CMD-SHELL", "node -e \"require('net').connect(8080,'127.0.0.1').on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))\""]
interval: 60s
timeout: 10s
retries: 5
start_period: 60s
security_opt:
- no-new-privileges:true
networks:
- app-net
- termix-net
guacd:
# https://hub.docker.com/r/guacamole/guacd - Apache Guacamole proxy daemon for termix RDP/VNC
# Backend only - reached by termix over termix-net. Deliberately NOT published to the host.
image: guacamole/guacd:1.6.0
container_name: guacd
hostname: guacd
restart: always
expose:
- "4822"
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=guacd
- com.centurylinklabs.watchtower.enable=false
environment:
- TZ=Australia/Perth
volumes:
- /etc/localtime:/etc/localtime:ro
security_opt:
- no-new-privileges:true
networks:
- termix-net
watchtower:
# https://marrrrrrrrry.github.io/watchtower-docs/quick-start.html
image: docker.io/marrrrrrrrry/watchtower:latest
container_name: watchtower
hostname: watchtower
restart: always
labels:
- com.docker.compose.project=utilities-stack
- com.docker.compose.service=watchtower
- com.centurylinklabs.watchtower.enable=true
environment:
- DOCKER_CLIENT_TIMEOUT=300
- COMPOSE_HTTP_TIMEOUT=300
- WATCHTOWER_POLL_INTERVAL=21600
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_LOG_LEVEL=info
- TZ=Australia/Perth
# --- Monitor only: report available updates, do not apply them ---
- WATCHTOWER_MONITOR_ONLY=true
# --- SMTP Notifications ---
- WATCHTOWER_NOTIFICATIONS=email
- WATCHTOWER_NOTIFICATION_EMAIL_FROM={WATCHTOWER_NOTIFICATION_EMAIL_FROM}
- WATCHTOWER_NOTIFICATION_EMAIL_TO={WATCHTOWER_NOTIFICATION_EMAIL_TO}
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER={WATCHTOWER_NOTIFICATION_EMAIL_SERVER}
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT={WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT}
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER={WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER}
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD={WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD}
- WATCHTOWER_NOTIFICATION_EMAIL_DELAY={WATCHTOWER_NOTIFICATION_EMAIL_DELAY}
- WATCHTOWER_NOTIFICATION_REPORT={WATCHTOWER_NOTIFICATION_REPORT}
- WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG={WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG}
- WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY=true
volumes:
# Change this socket as required whether deploying docker or podman
- /run/podman/podman.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
security_opt:
- no-new-privileges:true
networks:
- app-net
volumes:
bentopdf-config:
bentopdf-data:
bentopdf-logs:
networks:
app-net:
external: true
termix-net:
driver: bridgeI hope this helps. It is a very good multiservice stack, but you will probably need to adjust ports and make considerable alterations to your reverse proxy. If you dont have a reverse proxy just ensure when you set up Portainer CE that you SETUP a public IP for the server environment, which will require a DNS record yet again.
#enoughsaid