podman-compose requires all networks referenced in service configs to be
explicitly declared in the top-level networks block. Docker Compose
creates the default network implicitly, but podman-compose errors with
'missing networks: default'.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
One Postgres 16 instance runs in the infra stack (docker-compose).
Each app can be given its own isolated schema with a dedicated,
scoped Postgres user via the new Database card on the app detail page.
What was added:
infra/
docker-compose.yml — postgres:16-alpine service + hiy-pg-data
volume; POSTGRES_URL injected into server
.env.example — POSTGRES_PASSWORD entry
server/
Cargo.toml — sqlx postgres feature
src/db.rs — databases table (SQLite) migration
src/models.rs — Database model
src/main.rs — PgPool (lazy) added to AppState;
/api/apps/:id/database routes registered
src/routes/mod.rs — databases module
src/routes/databases.rs — GET / POST / DELETE handlers:
provision — creates schema + scoped PG user, sets search_path,
injects DATABASE_URL env var
deprovision — DROP OWNED BY + DROP ROLE + DROP SCHEMA CASCADE,
removes SQLite record
src/routes/ui.rs — app_detail queries databases table, renders
db_card based on provisioning state
templates/app_detail.html — {{db_card}} placeholder +
provisionDb / deprovisionDb JS
Apps connect via:
postgres://hiy-<app>:<pw>@postgres:5432/hiy
search_path is set on the role so no URL option is needed.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Two root causes:
1. Caddy was started without --resume, so every restart wiped all
dynamically-registered app routes (only the base Caddyfile survived).
Adding --resume makes Caddy reload its auto-saved config (stored in
the caddy-config volume) which includes all app routes.
2. App routes used the container IP address, which changes whenever
hiy-net is torn down and recreated by compose. Switch to the
container name as the upstream dial address; Podman's aardvark-dns
resolves it by name within hiy-net, so it stays valid across
network recreations.
Together with the existing reconnect loop in start.sh these two
changes mean deployed apps survive a platform restart without needing
a redeploy.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
build.sh calls `podman build` inside the server container.
DOCKER_HOST is a Docker CLI variable; Podman does not use it to
automatically switch to remote mode. Without CONTAINER_HOST set,
Podman runs locally inside the (unprivileged) container, has no
user-namespace support, and lchown fails for any layer file owned
by a non-zero GID (e.g. gid=42 for /etc/shadow).
Setting CONTAINER_HOST=tcp://podman-proxy:2375 makes Podman
automatically operate in remote mode and delegate all operations
to the host Podman service, which has the correct subuid/subgid
mappings and full user-namespace support.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
start.sh now activates the Podman user socket via systemctl --user if it
isn't running yet, then exports DOCKER_HOST and PODMAN_SOCK so that
podman compose (which delegates to the docker-compose plugin) can connect.
docker-compose.yml mounts ${PODMAN_SOCK} into the socat proxy container
at a fixed internal path (/podman.sock), so it works for both rootful
(/run/podman/podman.sock) and rootless (/run/user/<UID>/podman/podman.sock)
without hardcoding the UID.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
- New HIY_ADMIN_USER / HIY_ADMIN_PASS env vars control access
- Login page at /login with redirect-after-login support
- Cookie-based sessions (HttpOnly, SameSite=Strict); cleared on restart
- Auth middleware applied to all routes except /webhook/:app_id (HMAC) and /login
- Auth is skipped when credentials are not configured (dev mode, warns at startup)
- Logout link in both dashboard nav bars
- Caddy admin port 2019 no longer published to the host in docker-compose
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Using compose-level ${DOMAIN_SUFFIX} substitution only works when docker
compose is run from the same directory as the .env file. env_file loads
the file relative to the compose file, so it works regardless of CWD.
Caddy's email directive requires a non-empty argument. Since ACME_EMAIL
wasn't set, Caddy failed to parse the config. Email is optional for
Let's Encrypt — remove the directive entirely and document it as a
manual opt-in comment.
Caddy's built-in ACME support handles TLS automatically — no CF_API_TOKEN,
no Cloudflare account, no DNS plugin needed. Requires ports 80+443 forwarded
to the Pi and ACME_EMAIL set in infra/.env.
Remove hardcoded platform from compose file so plain 'make up' (or
'docker compose up --build') always builds natively for the host.
Explicit targets (up-arm64, up-armv7, etc.) set DOCKER_DEFAULT_PLATFORM.
Dockerfile now uses BuildKit TARGETARCH/TARGETVARIANT to pick the Rust
cross-compilation target automatically. The build stage always runs on
the host platform for speed.
Makefile provides named targets:
make up-amd64 # Mac Intel / Linux desktop
make up-arm64 # Mac M1/M2/M3, Pi 4/5 (64-bit OS)
make up-armv7 # Pi 2/3/4 (32-bit OS)
make up-armv6 # Pi Zero / Pi 1
- Add docker-proxy (alpine/socat) sidecar that exposes the Docker Unix
socket as TCP on port 2375, so server needs no privileged socket mount
- Set DOCKER_HOST=tcp://docker-proxy:2375 in server environment
- App containers are still spawned on the host daemon and join hiy-net,
so Caddy can still reach them
- Log actual Caddy PUT response body and HTTP status on failure
instead of a silent warning
The Caddyfile created a server with an auto-generated name, not 'hiy',
so build.sh's PUT to /config/apps/http/servers/hiy/routes was creating
a parallel server that never received traffic.
- Replace Caddyfile with caddy.json that names the server 'hiy' with
the dashboard as a catch-all fallback route
- Insert app routes at index 0 so host-matched routes are evaluated
before the catch-all dashboard fallback
- Update docker-compose to mount caddy.json and pass --config flag