composer/README.md
padriano b896bfcb79 docs: document static host IP and DHCP pool for external access
Record the final networking setup: host static 192.168.1.2 (netplan),
HA/Samba macvlan .246/.247, DHCP pool narrowed to .11-.245 so the
statics stay protected, and why static-at-host beats DHCP reservation
on the NOS router. Hard-code 192.168.1.2 in the port-forward table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:46:45 +01:00

296 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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://server:8123` on the LAN
- 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 |