On a macvlan network a published port DNATs to the macvlan IP, which the host cannot reach, so http://<host>:8123 never worked. Remove the dead mapping; LAN access is direct via http://192.168.1.246:8123 and external access is via Caddy. Update README LAN URL accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
Docker Compose — Home Server
Services
| Container | Image | Purpose |
|---|---|---|
duckdns |
lscr.io/linuxserver/duckdns |
Dynamic DNS — keeps *.duckdns.org pointed at the home IP |
caddy |
caddy (custom build) |
HTTPS reverse proxy with automatic Let's Encrypt certificates via DuckDNS DNS-01 |
homeassistant |
homeassistant/home-assistant |
Home automation, reachable at https://ha-padriano.duckdns.org |
timemachine |
mbentley/timemachine:smb |
Time Machine backup server over SMB |
samba |
dperson/samba |
General SMB file share for Mac and Linux clients |
forgejo |
codeberg.org/forgejo/forgejo |
Self-hosted Git server at https://git-padriano.duckdns.org, SSH at :2222 |
Initial Setup
-
Copy and fill in the environment file:
cp .env.example .env nano .env -
Register the required DuckDNS subdomains at duckdns.org:
ha-padriano,padrianoha,padriano,git-padriano. -
Create the required host directories if they don't exist:
mkdir -p /home/padriano/projects/forgejo mkdir -p /home/padriano/shareThe Samba container runs as UID/GID 1001 internally, so the share directory must be owned by that user:
sudo chown 1001:1001 /home/padriano/share -
Add the reverse proxy trusted header config to Home Assistant:
sudo tee -a /home/padriano/projects/ha/config/configuration.yaml > /dev/null << 'EOF' http: use_x_forwarded_for: true trusted_proxies: - 172.16.0.0/12 EOF -
Bring the full stack up (first run builds the custom Caddy image):
cd /home/padriano/projects/composer docker compose up -d --build
Common Docker Compose Commands
Stack management
# Start all services (detached)
docker compose up -d
# Stop all services (containers remain)
docker compose stop
# Stop and remove all containers (data volumes are preserved)
docker compose down
# Restart all services
docker compose restart
# Restart a single service
docker compose restart homeassistant
Viewing status and logs
# Show running containers and their status
docker compose ps
# Follow logs for all services
docker compose logs -f
# Follow logs for a single service
docker compose logs -f forgejo
# Show last 50 lines for a service
docker compose logs --tail=50 timemachine
Updating images
# Pull latest images for all services
docker compose pull
# Pull for a single service
docker compose pull forgejo
# Recreate containers that have a newer image
docker compose up -d --pull always
# Recreate a single service after pulling
docker compose up -d --force-recreate forgejo
Inspecting containers
# Open a shell inside a running container
docker exec -it homeassistant bash
docker exec -it forgejo sh # alpine-based, use sh not bash
# Show resource usage (CPU, RAM, network)
docker stats
# Inspect container configuration
docker inspect forgejo
Service Notes
duckdns
- Config stored at
/home/padriano/projects/duckdns/config - Domains managed:
ha-padriano,padrianoha,padriano,git-padriano(all.duckdns.org) - Subdomains must be registered manually at duckdns.org before they can be updated
caddy
- Custom image built from
caddy/Dockerfile— adds the DuckDNS DNS provider plugin to the official Caddy image - Config at
caddy/Caddyfile; TLS certificates stored in thecaddy_dataDocker volume - Obtains free Let's Encrypt certificates via DNS-01 challenge (no extra ports needed, no cost)
- Listens on
:80(redirects to HTTPS) and:443 - Routes:
ha-padriano.duckdns.org→ Home Assistant:8123git-padriano.duckdns.org→ Forgejo:3000
- To reload config without restarting:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
homeassistant
- Config stored at
/home/padriano/projects/ha/config - Web UI:
https://ha-padriano.duckdns.org(via Caddy) orhttp://192.168.1.246:8123(direct to the macvlan IP) on the LAN - No host port is published for
8123— on a macvlan network a published port DNATs to the macvlan IP, which the host itself cannot reach. Use the macvlan IP directly on the LAN, or Caddy externally - Runs on the
internalbridge network so Caddy can reach it by container name configuration.yamlmust includehttp.use_x_forwarded_for: trueandtrusted_proxiesfor the Docker bridge range so HA accepts forwarded headers from Caddy- Has a healthcheck — status shows
(health: starting)for ~60s on first boot
timemachine
- Backup data stored at
/home/padriano/backup/timemachine - Uses
network_mode: hostand a fixedhostname: timemachine— the stable hostname prevents macOS from losing the backup destination after container restarts - On your Mac: System Settings → General → Time Machine → Add Backup Disk
samba
- Shared directory on the host:
/home/padriano/share— must be owned by1001:1001(the UID/GID the container uses internally); without this, the share mounts on the Mac but files cannot be written - Uses a macvlan network with static IP
192.168.1.247— avoids port 445 conflict with thetimemachinecontainer - Config stored at
samba/smb.conf; the[Share]section is the only exported share - Avahi service file at
samba/avahi-smb.serviceadvertises the share for Finder sidebar discovery (installed to/etc/avahi/services/samba.serviceon the host) - Connecting from a Mac: open Finder → Go → Connect to Server →
smb://192.168.1.247/Share - Connecting from Linux:
smbclient //192.168.1.247/Share -U sambaor mount withmount -t cifs //192.168.1.247/Share /mnt/share -o username=samba - Username and password are set via
SAMBA_USERNAME/SAMBA_PASSWORDin.env
forgejo
- All data (repos, config, SQLite DB) stored at
/home/padriano/projects/forgejo - Backing up this single directory is sufficient for a full restore
- Port 3000 is no longer exposed on the host — access is via Caddy only
First-run setup
Visit http://server:3000 on first start (LAN only) and complete the install wizard:
- Base URL:
https://git-padriano.duckdns.org - SSH domain:
git-padriano.duckdns.org - SSH port:
2222
Access
| Method | Local network | External (via DuckDNS) |
|---|---|---|
| Web UI | http://server:3000 |
https://git-padriano.duckdns.org |
| Git SSH | ssh://git@server:2222 |
ssh://git@git-padriano.duckdns.org:2222 |
For external access, forward these ports on your router to the server IP — see Networking & External Access below for the full setup.
Git remote URL examples
# Add remote via HTTPS
git remote add origin https://git-padriano.duckdns.org/padriano/my-repo.git
# Add remote via SSH
git remote add origin ssh://git@git-padriano.duckdns.org:2222/padriano/my-repo.git
# Clone via SSH (local)
git clone ssh://git@server:2222/padriano/my-repo.git
Networking & External Access
Address layout
The stack uses fixed addresses on the 192.168.1.0/23 LAN:
| Address | Belongs to | Assigned by |
|---|---|---|
192.168.1.2 |
The server host itself (Docker publishes Caddy :443/:80 and Forgejo :2222 here) |
Static at the OS level — netplan /etc/netplan/99-server-static.yaml on interface enp0s31f6 |
192.168.1.246 |
homeassistant macvlan interface |
Static (docker-compose.yml) |
192.168.1.247 |
samba macvlan interface |
Static (docker-compose.yml) |
Caddy and Forgejo publish their ports on the host IP (192.168.1.2, via Docker port mapping) — not on 192.168.1.246. The macvlan IP belongs only to the Home Assistant container; Caddy reaches HA over the internal bridge network by container name.
Keeping these IPs stable (router DHCP)
All three static IPs must sit outside the router's DHCP pool so the router can never lease them to another device. On the NOS F@st 5670 the DHCP pool is set to:
- Pool start
192.168.1.11, end192.168.1.245— this leaves.2–.10and.246–.254free for static use.
Why static-at-the-host instead of a DHCP reservation: the NOS router only reserves a device's current lease, and refuses to reserve any IP already present in the DHCP leases table. Combined with the macvlan containers (which don't use DHCP at all and never appear in the leases table), reservations proved unworkable. Pinning the host IP in netplan and carving the static range out of the DHCP pool is the reliable approach.
History: the host originally took
192.168.1.246from DHCP — the same IP statically assigned to the Home Assistant container — causing an address collision (two MACs answering for.246). Moving the host to a static.2outside the pool resolved it.
To re-apply the host static IP after a fresh OS install, recreate /etc/netplan/99-server-static.yaml:
network:
version: 2
renderer: NetworkManager
ethernets:
enp0s31f6:
dhcp4: false
addresses:
- 192.168.1.2/23
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [192.168.1.1, 1.1.1.1]
Then sudo netplan apply. Cloud-init network management is disabled via /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg (network: {config: disabled}) so it doesn't override this on reboot.
Router port forwarding
Forward exactly two ports to the host IP 192.168.1.2. The single 443 rule serves both sites — Caddy routes by hostname.
| Service | Protocol | External port | Internal host | Internal port |
|---|---|---|---|---|
| HTTPS (Caddy → Home Assistant and Forgejo) | TCP | 443 | 192.168.1.2 |
443 |
| Forgejo SSH | TCP | 2222 | 192.168.1.2 |
2222 |
Do not forward:
8123(Home Assistant) — bypasses Caddy and exposes the UI over plaintext HTTP. Usehttps://ha-padriano.duckdns.orginstead.3000(Forgejo) — not exposed on the host; access is via Caddy only.22— not used by this stack; Forgejo SSH is on2222.
Port 80 is optional — forward it only if you want bare http:// requests auto-redirected to HTTPS. It is not required for certificates, which use the DuckDNS DNS-01 challenge.
Disk Space
# Free space on the main filesystem
df -h /
# Free space on all mounted filesystems
df -h
# Space used by each service's data directory
du -sh /home/padriano/projects/* /home/padriano/backup/*
Data & Backup Summary
| Service | Host path | Notes |
|---|---|---|
duckdns |
/home/padriano/projects/duckdns/config |
Token cache, not critical |
caddy |
Docker volume caddy_data |
Issued TLS certificates — auto-reissued if lost |
homeassistant |
/home/padriano/projects/ha/config |
All HA config and automations |
timemachine |
/home/padriano/backup/timemachine |
Mac backup sparsebundles |
samba |
/home/padriano/share |
General file share — back up as needed |
forgejo |
/home/padriano/projects/forgejo |
All repos, DB, config |