Secure Docker Compose Homelab Stack with Caddy Reverse Proxy
Last updated: August 2026
Tested on: Ubuntu 24.04 LTS, Docker Engine [add your tested version], Docker Compose v2 [add your tested version], Caddy 2, Uptime Kuma 2, LAN-only server at 192.168.1.20. Replace this line with your real test environment before publishing.
Most Docker Compose examples are written to get the service running quickly.
That is fine when you are testing something on a spare evening. It is less fine six months later, when the home server has ten containers, five open ports, two forgotten dashboards, one mystery database volume and no clear restore process.
This guide shows how I would build a secure Docker Compose homelab stack before adding Jellyfin, Paperless, Gitea, Home Assistant, dashboards, media tools, databases or anything else that slowly turns a Linux box into “production” at home.
This is not enterprise Kubernetes. It is not a polished corporate platform. It is a practical Docker Compose homelab baseline built around a simple idea:
Publish one reverse proxy. Keep backend services private. Make the stack boring enough to maintain later.
Boring is good. Boring survives reboots, updates, tired evenings and those “why is this admin panel exposed on port 9443?” moments.
Quick answer: what is the safest Docker Compose homelab pattern?
The safest beginner-friendly pattern for a Docker Compose homelab is to expose only a reverse proxy on ports 80 and 443, keep application containers on private Docker networks, avoid privileged: true, avoid mounting the Docker socket by default, store persistent data under a clear folder such as /srv/docker, and back up the Compose files plus application data before updates.
For this guide, the stack looks like this:
LAN or internet
-> router / firewall
-> Caddy reverse proxy on ports 80 and 443
-> private Docker network
-> Uptime Kuma
-> future homelab services
The key habit is simple:
Use
portsfor the reverse proxy. Useexposeor private Docker networking for backend web services.
For most homelab dashboards, this is the pattern to avoid:
ports:
- "8080:80"
This is usually the safer pattern:
expose:
- "80"
networks:
- proxy
The application stays reachable to the Caddy reverse proxy inside Docker, but it is not directly published on the host network.
Who this guide is for
This guide is for you if you run, or plan to run, self-hosted services on a Linux home server and want a cleaner starting point than a pile of random Compose files copied from tutorials.
It fits setups like:
- an old Dell, HP workstation, ThinkCentre, mini PC, NUC or laptop used as a home server;
- a Debian or Ubuntu server running Docker Compose;
- a home network where some services are LAN-only;
- a small VPS used like a homelab server;
- a Docker host that already has too many published ports;
- a new self-hosting setup where you want the baseline to be sane from day one.
If the Linux host itself is not hardened yet, start with the broader Linux Home Server Security Guide and the Linux Home Server Security Checklist. This Docker stack should sit on top of a patched, firewalled and backed-up host.
Table of contents
- What this secure homelab stack includes
- Why use Caddy as the reverse proxy?
- Security rules for this starter stack
- Prerequisites before you begin
- Build the secure Docker Compose homelab stack
- How to add new services safely
- A safer database pattern
- What I would not add to a starter stack
- Backups and restore notes
- Monthly maintenance checklist
- Common mistakes
- Frequently asked questions
What this secure homelab stack includes
This Docker Compose starter stack includes:
- Caddy as the only published web entry point;
- Uptime Kuma for basic service monitoring;
- whoami as a temporary reverse proxy test container;
- a clean
/srv/docker/starter-stackfolder layout; - a local
.envfile for non-secret settings; - private Docker networking for backend services;
- no direct published port for Uptime Kuma;
- no Docker socket mount;
- no privileged containers;
- a simple backup and restore pattern.
The idea is not to include every useful homelab tool. The idea is to build a foundation you can understand later.
A starter stack should not be impressive. It should be clear.
Why use Caddy as the reverse proxy?
I am using Caddy here because it is a good default for a small homelab:
- the configuration is short;
- HTTPS is simple for real domains;
- the Caddyfile is easy to keep with the stack;
- there is no management dashboard to expose;
- basic reverse proxy use does not require the Docker socket;
- it keeps the setup understandable.
This does not mean Caddy is always the best reverse proxy. If you prefer Nginx Proxy Manager because you want a web UI, or Traefik because your homelab is heavily label-driven, that is fine.
The security model should stay the same:
One public or LAN-facing entry point. Backend containers stay private unless they genuinely need a host port.
For a deeper comparison, see the existing reverse proxy guide for home servers.
Security rules for this starter stack
Before touching the terminal, these are the rules for this stack.
1. Caddy is the only service with published web ports
Caddy gets host ports 80 and 443. Uptime Kuma does not get 3001:3001. The test container does not get a host port either.
2. Backend services stay on a private Docker network
The backend services are reachable by Docker service name from Caddy, not by random ports exposed on the server.
3. No Docker socket mount by default
This stack does not mount:
/var/run/docker.sock
Some tools need it. Many tutorials ask for it casually. That does not make it harmless. If a container can talk to the Docker daemon, treat it like a high-trust component.
4. No privileged containers
This starter stack does not use:
privileged: true
If a normal dashboard or web app needs privileged mode, I want to know why before it goes anywhere near the server.
5. Keep persistent data obvious
The data lives under:
/srv/docker/starter-stack
That is easy to find, easy to back up and easy to restore.
6. Backups are part of security
A stack is not finished just because the dashboard loads. A stack is finished when you know how to restore it.
For a deeper backup plan, see Backing Up Docker Containers: The Homelab Disaster You Can Avoid.
Prerequisites before you begin
This guide assumes:
- a Linux server, usually Debian or Ubuntu;
- Docker Engine installed;
- Docker Compose v2 installed;
- UFW or another host firewall configured;
- a LAN IP such as
192.168.1.20; - local DNS or a public domain name;
- a backup location;
- basic comfort editing files in the terminal.
If SSH is not hardened yet, fix that first. The SSH hardening guide for home servers covers keys, firewall rules and safe remote access.
If your firewall rules are still guesswork, read UFW Firewall Rules for Home Servers before exposing anything.
Build the secure Docker Compose homelab stack
This is the practical build section. Do not skip the testing steps. The whole point is to confirm that only the intended services are reachable.
1. Plan the stack and DNS names
Decide whether this stack is:
- LAN-only, reachable only inside your home network;
- VPN-only, reachable through WireGuard, Tailscale or another VPN;
- public, reachable from the internet on ports
80and443.
For many homelabs, LAN-only or VPN-only is the better default.
Example values used in this guide:
Server IP: 192.168.1.20
Uptime Kuma: uptime.example.com
Test service: test.example.com
Timezone: Europe/Lisbon
For a LAN-only setup, you might use local names instead:
uptime.home.arpa
test.home.arpa
Your local DNS must point those names to the server IP. Caddy cannot fix DNS that points nowhere.
2. Create the Docker Compose folder layout
I keep homelab Docker services under /srv/docker. It is not magic. It is just easy to remember.
Create the folder structure:
sudo mkdir -p /srv/docker/starter-stack/{caddy/data,caddy/config,uptime-kuma/data}
sudo chown -R "$USER:$USER" /srv/docker/starter-stack
cd /srv/docker/starter-stack
The layout will look like this:
/srv/docker/starter-stack/
compose.yml
Caddyfile
.env
RESTORE.md
caddy/
data/
config/
uptime-kuma/
data/
This keeps the recipe and the data together:
compose.ymldescribes the containers;Caddyfiledescribes reverse proxy routing;.envkeeps local values out of the Compose file;RESTORE.mdexplains how to rebuild the stack;- service folders hold persistent application data.
When something breaks, clarity matters.
3. Create the environment file
Create the .env file:
nano .env
Example:
TZ=Europe/Lisbon
BIND_IP=192.168.1.20
DOMAIN_UPTIME=uptime.example.com
DOMAIN_TEST=test.example.com
Change these values for your setup.
BIND_IP should normally be the server LAN IP for a LAN-first homelab. If you intentionally want Caddy to listen on every interface, use:
BIND_IP=0.0.0.0
Do not do that by habit. Do it only when that is really the exposure you want.
Protect the file:
chmod 600 .env
An .env file is not a secrets vault. It is just a file. Do not commit it to a public Git repository.
If you keep the stack in Git, add this:
echo ".env" >> .gitignore
4. Create the Docker Compose file
Create the Compose file:
nano compose.yml
Paste this:
name: homelab-starter
x-common: &common
restart: unless-stopped
security_opt:
- no-new-privileges:true
logging:
driver: local
services:
caddy:
<<: *common
image: caddy:2
ports:
- "${BIND_IP}:80:80"
- "${BIND_IP}:443:443"
environment:
TZ: ${TZ}
DOMAIN_UPTIME: ${DOMAIN_UPTIME}
DOMAIN_TEST: ${DOMAIN_TEST}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy/data:/data
- ./caddy/config:/config
networks:
- proxy
uptime-kuma:
<<: *common
image: louislam/uptime-kuma:2
expose:
- "3001"
volumes:
- ./uptime-kuma/data:/app/data
networks:
- proxy
whoami:
<<: *common
image: traefik/whoami:v1.10
expose:
- "80"
read_only: true
cap_drop:
- ALL
networks:
- proxy
networks:
proxy:
name: homelab_proxy
The important part is not the specific demo service. The important part is the exposure model.
Caddy is the only service with:
ports:
Uptime Kuma and whoami use:
expose:
That documents the internal container ports without publishing them on the host.
In plain English:
- Caddy listens on the server.
- Caddy talks to backend containers by service name.
- The backend containers do not get random host ports.
That is the pattern you should reuse when adding more homelab services.
5. Create the Caddy reverse proxy configuration
Create the Caddyfile:
nano Caddyfile
For a real domain, use this pattern:
{
email admin@example.com
}
{$DOMAIN_UPTIME} {
encode zstd gzip
reverse_proxy uptime-kuma:3001
}
{$DOMAIN_TEST} {
reverse_proxy whoami:80
}
Replace admin@example.com with your real admin email.
For a LAN-only setup using internal names, you can use Caddy internal TLS:
{
email admin@example.com
}
uptime.home.arpa {
tls internal
encode zstd gzip
reverse_proxy uptime-kuma:3001
}
test.home.arpa {
tls internal
reverse_proxy whoami:80
}
For LAN-only names, make sure your local DNS points to the server:
uptime.home.arpa -> 192.168.1.20
test.home.arpa -> 192.168.1.20
If you use Caddy internal TLS, browsers may require you to trust the internal CA certificate. That is normal. Do not confuse local certificate trust with a broken reverse proxy.
6. Validate and start the stack
Before starting anything, inspect the final Compose configuration:
docker compose -f compose.yml config
This catches simple mistakes and shows the resolved configuration after variables are applied.
Pull the images:
docker compose -f compose.yml pull
Start the stack:
docker compose -f compose.yml up -d
Check the containers:
docker compose -f compose.yml ps
Then check published ports:
docker ps --format "table {{.Names}}\t{{.Ports}}"
You want output roughly like this:
NAMES PORTS
homelab-starter-caddy-1 192.168.1.20:80->80/tcp, 192.168.1.20:443->443/tcp
homelab-starter-uptime-kuma-1 3001/tcp
homelab-starter-whoami-1 80/tcp
The names may differ. The important part is this:
- Caddy publishes
80and443. - Uptime Kuma does not publish
3001on the host. - whoami does not publish a host port.
If Uptime Kuma shows something like this, stop and fix it:
0.0.0.0:3001->3001/tcp
That means it is directly published. That is not the design of this stack.
7. Configure UFW firewall rules
For a LAN-only homelab, I would start with rules like this:
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 80 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 443 proto tcp
sudo ufw status verbose
Change 192.168.1.0/24 to your real LAN range.
For a public web service, the web rules change:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
Keep SSH restricted if possible. A common pattern is public HTTP/HTTPS, but SSH only from the LAN or VPN.
There is an important Docker warning here. Docker published ports may not behave the way beginners expect when UFW is active. That is why this stack avoids publishing backend ports in the first place.
Do not rely on UFW to rescue a messy Compose file. Fix the Compose file.
For more detail, see Docker and UFW: Why Your Firewall Rules Do Not Protect Published Containers.
8. Test published ports from another machine
Do not only test from the Docker host. Test from another computer on the LAN.
For a public or normal HTTPS domain:
curl -I https://uptime.example.com
curl -I https://test.example.com
For a LAN-only internal TLS setup:
curl -k -I https://uptime.home.arpa
curl -k -I https://test.home.arpa
Now test that Uptime Kuma is not directly reachable by IP and port:
curl -I http://192.168.1.20:3001
From another LAN machine, that should fail.
If it works, check published ports again:
docker ps --format "table {{.Names}}\t{{.Ports}}"
Something is probably publishing 3001.
9. Configure Uptime Kuma monitoring
Open Uptime Kuma through the reverse proxy:
https://uptime.example.com
Create the admin account.
Then add a few useful monitors:
- the Uptime Kuma URL through Caddy;
- the temporary test URL;
- your router IP;
- your server SSH port from the LAN;
- important self-hosted services;
- DNS checks for local or public names;
- certificate expiry checks where useful.
Do not monitor everything every ten seconds. That creates noise.
For a normal home server, sensible intervals are usually:
- critical local services: 30 to 60 seconds;
- normal dashboards: 60 to 120 seconds;
- public websites: 60 to 300 seconds;
- non-critical devices: 300 seconds or more.
Uptime Kuma is there to catch boring failures early: a container stopped, a proxy route broke, a service is slow, a certificate is close to expiring or DNS points to the wrong place.
For a full monitoring setup, see Uptime Kuma for Home Servers.
10. Remove the temporary test container
The whoami container is useful while testing. It should not live forever.
After the proxy works, remove this service from compose.yml:
whoami:
<<: *common
image: traefik/whoami:v1.10
expose:
- "80"
read_only: true
cap_drop:
- ALL
networks:
- proxy
Then remove the test route from Caddyfile:
{$DOMAIN_TEST} {
reverse_proxy whoami:80
}
Apply the change:
docker compose -f compose.yml up -d --remove-orphans
Check again:
docker compose -f compose.yml ps
docker ps --format "table {{.Names}}\t{{.Ports}}"
Old test services are how “temporary” becomes “why is this still running?” three years later.
11. Back up the stack and write restore notes
This folder should be included in your normal backup plan:
/srv/docker/starter-stack/
At minimum, back up:
compose.yml;Caddyfile;.env;RESTORE.md;caddy/data;caddy/config;uptime-kuma/data;- future app folders.
Example backup:
sudo rsync -aAX --info=progress2 \
/srv/docker/starter-stack/ \
/mnt/backup/homeserver/starter-stack/
Caddy data may include certificates and keys. Treat the backup accordingly.
Now create a restore note:
nano RESTORE.md
Example:
# Restore notes for homelab starter stack
1. Install Docker Engine and Docker Compose v2.
2. Copy this folder back to:
/srv/docker/starter-stack
3. Check ownership:
sudo chown -R USER:USER /srv/docker/starter-stack
4. Check .env values:
BIND_IP
DOMAIN_UPTIME
TZ
5. Validate the Compose file:
docker compose -f compose.yml config
6. Start the stack:
docker compose -f compose.yml up -d
7. Check published ports:
docker ps --format "table {{.Names}}\t{{.Ports}}"
8. Open Uptime Kuma through the reverse proxy.
9. Confirm monitors, notifications and certificates are present.
This file is not glamorous. That is why it works.
When a server fails, you do not want to rebuild your setup from old browser tabs and memory.
How to add new services safely
Once the starter stack works, this is the pattern I would use for most new web services:
myapp:
<<: *common
image: example/myapp:1.2.3
expose:
- "8080"
volumes:
- ./myapp/config:/config
- ./myapp/data:/data
networks:
- proxy
Then add a Caddy route:
myapp.example.com {
reverse_proxy myapp:8080
}
Notice what is missing:
ports:
- "8080:8080"
That is intentional.
Only publish host ports when the service genuinely needs to be reachable directly from outside Docker. Most web dashboards do not.
A good Docker Compose homelab should not turn into this:
service1 -> 0.0.0.0:3000
service2 -> 0.0.0.0:8080
service3 -> 0.0.0.0:9000
service4 -> 0.0.0.0:9443
service5 -> 0.0.0.0:5000
That works until nobody remembers what half the ports do.
A cleaner setup is:
Caddy:
80 and 443
Backend containers:
private Docker network
For the general hardening principles behind this, read Docker Security for Homelab Beginners.
A safer database pattern for Docker Compose
Databases should not be casually published to the LAN.
This is usually wrong for a homelab application database:
ports:
- "5432:5432"
If only one application needs PostgreSQL, keep the database on a private internal Docker network.
Example pattern:
services:
app:
image: example/app:1.2.3
expose:
- "8080"
networks:
- proxy
- app_internal
db:
image: postgres:17
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
secrets:
- postgres_password
volumes:
- ./postgres/data:/var/lib/postgresql/data
networks:
- app_internal
secrets:
postgres_password:
file: ./secrets/postgres_password
networks:
proxy:
name: homelab_proxy
app_internal:
internal: true
Create the secret file:
mkdir -p secrets
openssl rand -base64 32 > secrets/postgres_password
chmod 600 secrets/postgres_password
Not every image supports _FILE environment variables. Check the image documentation before assuming it works.
The point is not that every home server needs a complicated secrets setup on day one. The point is that database passwords do not belong pasted into public screenshots, old Git repos or backup notes with no thought.
Bind mounts: useful, but not harmless
This starter stack uses bind mounts because they make the file layout obvious:
volumes:
- ./uptime-kuma/data:/app/data
That means the data lives here:
/srv/docker/starter-stack/uptime-kuma/data
Easy to find. Easy to copy. Easy to restore.
But bind mounts are also powerful. A container with write access to a host folder can modify that folder.
Good pattern:
volumes:
- ./app/data:/data
- ./app/config:/config:ro
Bad pattern:
volumes:
- /:/host
- /etc:/etc
- /home:/home
Mount the smallest path that works. Use :ro when the container only needs to read a file or folder.
What I would not add to a secure starter stack
Some tools are useful later. That does not mean they belong in the first stack.
Portainer
Portainer can be useful, but it usually needs serious Docker access. I would learn the Compose files first, then add a management UI later if I still wanted one.
Watchtower
Automatic updates are convenient until they break an important service while you are not paying attention.
For important services, I prefer:
docker compose pull
docker compose up -d
Do it after checking backups and release notes.
Docker log dashboards with socket access
Tools like log dashboards can be useful, but many need Docker socket access. That may be acceptable in a controlled setup. It is not a default starter-stack choice for me.
Databases with published ports
PostgreSQL, MariaDB and Redis should not be published just because a tutorial did it.
If only containers need them, keep them inside Docker networks.
Anything with privileged mode
If a service needs this:
privileged: true
understand why before adding it.
Do not normalize privileged containers in a beginner stack.
Updating the stack
A simple manual update routine:
cd /srv/docker/starter-stack
docker compose -f compose.yml pull
docker compose -f compose.yml up -d
docker image prune
For important services, back up first.
I do not like blind auto-updates for a home server that stores real data.
A broken container is annoying. A broken container with no backup is a lesson you did not need.
Backups for a Docker Compose homelab
A good Docker backup plan is not about backing up containers. Containers are usually disposable.
You back up:
- Compose files;
.envfiles;- reverse proxy configuration;
- application data;
- named volumes, if used;
- database dumps, if needed;
- restore notes;
- scripts used to rebuild the stack.
For this starter stack, the easiest target is:
/srv/docker/starter-stack/
Example:
sudo mkdir -p /mnt/backup/homeserver/docker/
sudo rsync -aAX --info=progress2 \
/srv/docker/starter-stack/ \
/mnt/backup/homeserver/docker/starter-stack/
Be careful with secrets. Backups often contain the most sensitive version of your server: configuration files, credentials, certificates and private data all in one place.
If you copy backups to cloud storage, encrypt them first.
Monthly maintenance checklist
Once a month, run through this:
cd /srv/docker/starter-stack
docker compose -f compose.yml ps
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"
docker system df
sudo ufw status verbose
df -h
Ask:
- Is Caddy still the only published web entry point?
- Did a new service accidentally expose a port?
- Are old test containers gone?
- Is disk space healthy?
- Are backups running?
- Have I tested a restore recently?
- Are important images wildly outdated?
- Does Uptime Kuma still alert properly?
- Do DNS names still point where expected?
- Are there any temporary firewall rules that became permanent?
This is enough for many small home servers.
Not perfect. Not enterprise. Much better than ignoring it.
Final checklist for the secure homelab stack
- Caddy is the only service publishing ports
80and443. - Uptime Kuma is reachable through the reverse proxy.
- Uptime Kuma is not reachable directly on
server-ip:3001. - The
.envfile is not committed to Git. - The
.envfile has restricted permissions. - No container uses
privileged: true. - No container mounts
/var/run/docker.sock. - No container mounts
/from the host. - UFW rules match the intended exposure.
- DNS records point to the server.
- Backups include
/srv/docker/starter-stack. RESTORE.mdexists.- Uptime Kuma monitors the important services.
- The temporary whoami service has been removed.
Common mistakes with Docker Compose homelabs
Publishing every container port
This is the classic mistake:
ports:
- "8080:80"
Sometimes it is necessary. Often it is just copied from an example. For web services behind a reverse proxy, ask whether the container really needs a host port.
Trusting UFW without testing Docker exposure
UFW may say incoming traffic is denied, but Docker-published ports can still surprise people. Always test from another machine.
Leaving test containers running
A test service should have a removal date. If it has been running for months, it is no longer a test. It is undocumented infrastructure.
Using latest everywhere
This is convenient:
image: someapp:latest
It can also surprise you later.
For important services, consider pinning at least the major version:
image: postgres:17
image: caddy:2
image: louislam/uptime-kuma:2
You still update. You just choose when.
Not knowing where the data lives
If you cannot answer where a service stores its data, you do not have a backup plan yet.
Backing up after the update breaks
The backup that matters is the one from before the risky change.
Frequently asked questions
Is Docker Compose safe for a homelab?
Docker Compose can be safe enough for many homelabs if you understand what it is doing. The biggest problems usually come from publishing too many ports, using privileged containers, mounting dangerous host paths, exposing admin dashboards and not backing up persistent data.
Should I use Caddy, Nginx Proxy Manager or Traefik?
Use Caddy if you want simple text configuration and easy HTTPS. Use Nginx Proxy Manager if you want a friendly web UI. Use Traefik if your homelab is heavily Docker Compose-based and you want routing to live in labels. The best choice is the one you can maintain without guessing.
Should Uptime Kuma be exposed to the internet?
Usually no. For a homelab, I prefer Uptime Kuma to be LAN-only, VPN-only or behind a reverse proxy with strong protection. It is a monitoring dashboard, not something I want casually exposed.
Do I need expose in Docker Compose?
Containers on the same Docker network can often reach each other by service name even without expose. I still like using expose in beginner-friendly Compose files because it documents the intended internal port without publishing it to the host.
Is an .env file secure?
No. An .env file is just a file. It is useful for keeping local values out of the Compose file, but it still needs restricted permissions, careful backups and should not be committed to public Git repositories.
Do I need Docker secrets in a small homelab?
Not always, but they are useful when images support them. For simple services, a protected .env file may be enough. For databases and API keys, secrets are cleaner than putting passwords directly into Compose files.
Can I use this stack with Nginx Proxy Manager instead of Caddy?
Yes. The core idea is the same: publish the reverse proxy, keep backend services private, back up the proxy configuration and avoid direct random host ports. The Compose file and proxy configuration would change, but the security model stays the same.
Should I use Watchtower for automatic updates?
I would not start with automatic updates for important services. Manual updates with a backup first are less convenient but easier to understand. Automatic updates are acceptable only when you accept the risk and know how to roll back.
What is the first thing to check if a service does not work?
Check DNS, then Caddy logs, then container status, then published ports.
docker compose -f compose.yml ps
docker compose -f compose.yml logs caddy --tail=100
docker ps --format "table {{.Names}}\t{{.Ports}}"
Most problems are boring: wrong DNS, wrong service name, wrong internal port or a container that did not start.
Related homelab guides
This post should sit inside the Docker and security cluster. These are the natural next reads:
- Start Here: Linux Homelab and Security
- Linux Home Server Security Guide
- Linux Home Server Security Checklist
- SSH Hardening for Home Servers
- UFW Firewall Rules for Home Servers
- Docker Security for Homelab Beginners
- Docker and UFW: Why Firewall Rules Do Not Protect Published Containers
- Reverse Proxy for Home Servers: Caddy vs Nginx vs Traefik
- Uptime Kuma for Home Servers
- Backing Up Docker Containers
Official references
- Docker Compose file reference
- Docker Compose services reference
- Docker Compose secrets documentation
- Docker bind mounts documentation
- Caddy reverse_proxy directive
- Uptime Kuma installation documentation
Final thoughts
A secure Docker Compose homelab stack is not about adding every hardening option you can find.
It is about starting with a layout that does not create chaos.
One reverse proxy. Private backend services. Clear folders. No random published ports. No Docker socket by default. No privileged containers unless there is a very good reason. Backups before experiments. Monitoring before surprises.
That is the kind of setup I want on a home server.
Not because it is fancy.
Because it is easy to understand when something breaks.
Written by MS. MS is a Linux homelab and cybersecurity enthusiast who documents practical experiments with home servers, Docker, firewalls, backups, monitoring, honeypots and old hardware. The guides on IT Random Stuff are based on hands-on testing, real configurations and lessons learned from running Linux systems at home.
Comments
Post a Comment