# Raspberry Pi 5 Docker Run This server repository contains a Dockerized QSfera path for Raspberry Pi 5 and other 64-bit ARM Linux hosts. Files used for this deployment: - `Dockerfile` - `docker-compose.rpi5.yml` - `.env.example` ## What This Runs - QSfera server from this `Server` source tree. - A pinned production image tag by default: `qsfera-cloud-server:7.0.0-rpi5-stable`. - Build metadata injected into the binary through Docker build args: `QSFERA_VERSION`, `QSFERA_EDITION`, and `QSFERA_BUILD_DATE`. - QSfera config persisted under `/etc/qsfera` inside the container. - QSfera data persisted under `/var/lib/qsfera` inside the container. - Host-side config and data paths controlled by `QSFERA_RPI_CONFIG_PATH` and `QSFERA_RPI_DATA_PATH`. - Host-side backend port bound only to `127.0.0.1:9200`; external access should go through the reverse proxy and HTTPS domain. - Host-side proxy debug/metrics port bound only to `127.0.0.1:9205`. ## Prerequisites On Raspberry Pi 5 1. Install Docker Engine using the official Docker docs: `https://docs.docker.com/engine/install/debian/`. 2. Install the Docker Compose plugin using the official Docker docs: `https://docs.docker.com/compose/install/`. 3. Use a 64-bit OS. The compose file sets `platform: linux/arm64`, and the Dockerfile builds with `TARGETARCH=arm64`. 4. Put `QSFERA_RPI_CONFIG_PATH` and `QSFERA_RPI_DATA_PATH` on durable storage, not on a nearly full SD-card root filesystem. 5. Set `OC_URL` to the public HTTPS URL used by clients and the reverse proxy. 6. Keep `QSFERA_EDITION=stable` and a concrete `QSFERA_VERSION` for production rollouts. Leaving those unset falls back to the defaults in the compose file. 7. Keep Docker image and builder retention under control. The current Raspberry Pi deployment stores QSfera data on `/mnt/data`; Docker's own `data-root` must either be moved there during a maintenance window or pruned on a schedule. ## Start Run from the `Server` directory: ```bash cp .env.example .env docker compose -f docker-compose.rpi5.yml up -d --build ``` Verify the image metadata before rollout: ```bash docker compose -f docker-compose.rpi5.yml build docker run --rm --entrypoint /usr/bin/qsfera qsfera-cloud-server:7.0.0-rpi5-stable version --skip-services ``` Run the production checker from the `Server` directory after every rollout: ```bash PUBLIC_STATUS_URL=https://qsfera.example.com/status.php \ ./scripts/verify-rpi-production.sh ``` ## Stop ```bash docker compose -f docker-compose.rpi5.yml down ``` ## Logs ```bash docker compose -f docker-compose.rpi5.yml logs -f qsfera-cloud-server ``` ## Update After Pulling New Code ```bash docker compose -f docker-compose.rpi5.yml up -d --build ``` ## Verify Local backend status endpoint: ```bash curl -k https://127.0.0.1:9200/status.php ``` External reverse-proxied status endpoint: ```bash curl https://qsfera.example.com/status.php ``` Local Prometheus metrics endpoint: ```bash curl http://127.0.0.1:9205/metrics ``` Repeatable production gate from the `Server` directory: ```bash PUBLIC_STATUS_URL=https://qsfera.example.com/status.php \ REQUIRE_METRICS=true \ ./scripts/verify-rpi-production.sh ``` The checker validates: - Docker Compose config rendering. - Local presence of the pinned image. - Image metadata from `qsfera version --skip-services`. - Container running state, Docker health status, and container image tag. - Docker root location, root filesystem free space, and data filesystem free space. - Local and optional public `/status.php` JSON invariants: `installed=true`, `maintenance=false`, `needsDbUpgrade=false`, `edition=stable`, and `productversion=7.0.0`. - Optional Prometheus metrics reachability. The default free-space thresholds are 5 GiB for `/` and 20 GiB for `/mnt/data`. Override them with `MIN_ROOT_AVAILABLE_KB` and `MIN_DATA_AVAILABLE_KB` when the host has a documented capacity plan. ## Data Location Docker bind mounts: - host config: `${QSFERA_RPI_CONFIG_PATH}` - container config: `/etc/qsfera` - host data: `${QSFERA_RPI_DATA_PATH}` - container data: `/var/lib/qsfera` For the current Raspberry Pi deployment checked on 2026-06-09 after the metrics rollout, the existing container uses: - host config: `/mnt/data/qsfera/cloud-server/config` - host data: `/mnt/data/qsfera/cloud-server/data` - image: `qsfera-cloud-server:7.0.0-rpi5-stable` - public URL: `https://qsfera.kusoft.xyz` - loopback port: `127.0.0.1:9200` - loopback metrics port: `127.0.0.1:9205` - Docker data-root: `/mnt/data/docker` ## Reverse Proxy The server is intentionally bound to loopback. Put Caddy, Nginx, Traefik, or another reverse proxy in front of it for external HTTPS access. The inspected Raspberry Pi uses Caddy with this QSfera route: ```caddyfile qsfera.kusoft.xyz { encode zstd gzip handle { reverse_proxy https://127.0.0.1:9200 { transport http { tls_insecure_skip_verify versions 1.1 } } } } ``` Do not proxy `127.0.0.1:9205` to the public Internet. If a remote Prometheus server needs to scrape it, connect through a private tunnel or bind it to an internal-only interface and set `PROXY_DEBUG_TOKEN`. ## Monitoring And Alerts Minimum checks for the Raspberry Pi deployment: - Run `./scripts/verify-rpi-production.sh` from the `Server` directory by cron or systemd timer every 5 minutes with `PUBLIC_STATUS_URL` set to the public QSfera URL. - Alert when the script exits non-zero. - Alert on any `WARN:` line that persists for more than one day, especially Docker root outside `/mnt/data` or unavailable metrics. - Scrape `http://127.0.0.1:9205/metrics` locally or through a private tunnel. - Keep public HTTPS probing separate from local loopback probing so reverse-proxy and backend failures are distinguishable. - Keep `OC_LOG_LEVEL=info` for production; raise to `debug` only for a bounded incident window. For larger deployments, the full compose example already contains tracing and metrics wiring in `devtools/deployments/qsfera_full/monitoring_tracing/`. ## Backups And Restore Drill Back up the two bind-mounted paths from `.env`: - `QSFERA_RPI_CONFIG_PATH` - `QSFERA_RPI_DATA_PATH` Create an archive before every server image rollout: ```bash stamp="$(date -u +%Y%m%d-%H%M%S)" backup="/mnt/data/qsfera/backups/cloud-server-${stamp}.tar.gz" sudo tar -C /mnt/data/qsfera/cloud-server -czf "$backup" config data sudo tar -tzf "$backup" >/dev/null ``` Run a restore drill after backup automation changes and at least monthly: ```bash stamp="$(date -u +%Y%m%d-%H%M%S)" restore_root="/mnt/data/qsfera/restore-drills/${stamp}" sudo mkdir -p "$restore_root" sudo tar -C "$restore_root" -xzf "$backup" sudo test -d "$restore_root/config" sudo test -d "$restore_root/data" ``` For a full service restore test, copy `.env` to a drill-only file, point `QSFERA_RPI_CONFIG_PATH` and `QSFERA_RPI_DATA_PATH` at the extracted directories, bind `QSFERA_RPI_HOST_PORT` to an unused loopback port, run `docker compose -f docker-compose.rpi5.yml --env-file .env.restore config`, and only then start the drill container. Do not run a restore drill against the live config/data paths. ## Docker Data-Root And Retention Check where Docker stores images, layers, and build cache: ```bash docker info --format '{{.DockerRootDir}}' docker system df ``` Preferred production state on this Pi is Docker data-root under `/mnt/data`, because the QSfera bind-mounted data is already stored there. Move it only during a maintenance window: ```bash sudo systemctl stop docker sudo mkdir -p /mnt/data/docker sudo rsync -aHAX --numeric-ids /var/lib/docker/ /mnt/data/docker/ printf '{\n "data-root": "/mnt/data/docker"\n}\n' | sudo tee /etc/docker/daemon.json sudo systemctl start docker docker info --format '{{.DockerRootDir}}' docker compose -f docker-compose.rpi5.yml ps ``` If moving Docker data-root is postponed, keep conservative retention active: ```bash docker builder prune --filter 'until=168h' docker image prune --filter 'until=168h' docker system df ``` Do not run `docker system prune --volumes` on this host unless a current backup has been verified and the target volumes have been explicitly reviewed.