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>
297 lines
11 KiB
Markdown
297 lines
11 KiB
Markdown
# 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
|
||
|
||
1. Copy and fill in the environment file:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
nano .env
|
||
```
|
||
|
||
2. Register the required DuckDNS subdomains at [duckdns.org](https://www.duckdns.org): `ha-padriano`, `padrianoha`, `padriano`, `git-padriano`.
|
||
|
||
3. Create the required host directories if they don't exist:
|
||
|
||
```bash
|
||
mkdir -p /home/padriano/projects/forgejo
|
||
mkdir -p /home/padriano/share
|
||
```
|
||
|
||
The Samba container runs as UID/GID 1001 internally, so the share directory must be owned by that user:
|
||
|
||
```bash
|
||
sudo chown 1001:1001 /home/padriano/share
|
||
```
|
||
|
||
4. Add the reverse proxy trusted header config to Home Assistant:
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
5. Bring the full stack up (first run builds the custom Caddy image):
|
||
|
||
```bash
|
||
cd /home/padriano/projects/composer
|
||
docker compose up -d --build
|
||
```
|
||
|
||
---
|
||
|
||
## Common Docker Compose Commands
|
||
|
||
### Stack management
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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](https://www.duckdns.org) before they can be updated
|
||
|
||
### caddy
|
||
|
||
- Custom image built from `caddy/Dockerfile` — adds the [DuckDNS DNS provider plugin](https://github.com/caddy-dns/duckdns) to the official Caddy image
|
||
- Config at `caddy/Caddyfile`; TLS certificates stored in the `caddy_data` Docker 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 `:8123`
|
||
- `git-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) or `http://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 `internal` bridge network so Caddy can reach it by container name
|
||
- `configuration.yaml` must include `http.use_x_forwarded_for: true` and `trusted_proxies` for 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: host` and a fixed `hostname: 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 by `1001: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 the `timemachine` container
|
||
- Config stored at `samba/smb.conf`; the `[Share]` section is the only exported share
|
||
- Avahi service file at `samba/avahi-smb.service` advertises the share for Finder sidebar discovery (installed to `/etc/avahi/services/samba.service` on 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 samba` or mount with `mount -t cifs //192.168.1.247/Share /mnt/share -o username=samba`
|
||
- Username and password are set via `SAMBA_USERNAME` / `SAMBA_PASSWORD` in `.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](#networking--external-access) below for the full setup.
|
||
|
||
#### Git remote URL examples
|
||
|
||
```bash
|
||
# 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`, end `192.168.1.245`** — this leaves `.2`–`.10` and `.246`–`.254` free 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.246` from 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 `.2` outside the pool resolved it.
|
||
|
||
To re-apply the host static IP after a fresh OS install, recreate `/etc/netplan/99-server-static.yaml`:
|
||
|
||
```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. Use `https://ha-padriano.duckdns.org` instead.
|
||
- `3000` (Forgejo) — not exposed on the host; access is via Caddy only.
|
||
- `22` — not used by this stack; Forgejo SSH is on `2222`.
|
||
|
||
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
|
||
|
||
```bash
|
||
# 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 |
|