familynido/deploy
Pablo Fernández a308041d59 Initial commit
FamilyNido — a self-hosted PWA for a single household: shared calendar,
chores, meals, school agenda, health records and a family wall. One
instance per family, deployable with `docker compose` on any home
server.

Stack: .NET 10 (ASP.NET Core Minimal APIs) + EF Core 10 + PostgreSQL 16
on the backend, Angular 21 (standalone, signals, zoneless) + Tailwind
CSS v4 on the frontend, SignalR for realtime, optional OIDC alongside
local credentials, integration via a versioned `/api/v1/**` public API.

See README.md for the module overview and how to deploy.
2026-05-13 00:23:14 +02:00
..
nginx Initial commit 2026-05-13 00:23:14 +02:00
.env.example Initial commit 2026-05-13 00:23:14 +02:00
api-entrypoint.sh Initial commit 2026-05-13 00:23:14 +02:00
api.Dockerfile Initial commit 2026-05-13 00:23:14 +02:00
docker-compose.dev.yml Initial commit 2026-05-13 00:23:14 +02:00
docker-compose.prod.yml Initial commit 2026-05-13 00:23:14 +02:00
README.md Initial commit 2026-05-13 00:23:14 +02:00
web.Dockerfile Initial commit 2026-05-13 00:23:14 +02:00

Deployment

Two compose files live here, for two distinct scenarios:

File Purpose
docker-compose.prod.yml Pulls pre-built images from GHCR. This is what runs on the home server.
docker-compose.dev.yml Dev stack with Postgres only — auth runs on local credentials.

Images are built by .github/workflows/build-and-push.yml on every push to main and published to:

  • ghcr.io/<GHCR_OWNER>/familynido-api:latest
  • ghcr.io/<GHCR_OWNER>/familynido-web:latest

Each push also produces an immutable sha-<shortsha> tag for rollbacks.

First-time server setup

Prerequisites on the server (Ubuntu, Docker, Traefik on the ${TRAEFIK_NETWORK} network with a Let's Encrypt cert resolver).

  1. Create a Personal Access Token on GitHub with scope read:packages. GHCR is private for private repos, so the server needs to authenticate.

  2. Log in to GHCR on the server (one-time, the credentials are cached in ~/.docker/config.json):

    echo <PAT> | docker login ghcr.io -u <github-username> --password-stdin
    
  3. Prepare the deploy folder (e.g. /opt/familynido/):

    sudo mkdir -p /opt/familynido
    sudo chown $USER:$USER /opt/familynido
    cd /opt/familynido
    

    Copy deploy/docker-compose.prod.yml and deploy/.env.example from this repo into that folder, rename the example to .env, and fill it in with the real PocketId/Postgres credentials.

  4. Pull and start:

    docker compose -f docker-compose.prod.yml pull
    docker compose -f docker-compose.prod.yml up -d
    
  5. Traefik picks the service up via the labels and serves it at the ${TRAEFIK_HOST} you configured once Let's Encrypt issues the cert.

OIDC client id. The default OIDC_CLIENT_ID is familynido. If you use PocketID or another upstream IdP, create a client with that id (or change the value here to match what your IdP exposes).

Google Calendar credentials

The calendar module mirrors events from Google Calendar (read-only). The backend needs an OAuth client to drive the consent flow:

  1. Create a project at https://console.cloud.google.com and enable the Google Calendar API.

  2. In APIs & Services → OAuth consent screen, configure an external app and add the scope https://www.googleapis.com/auth/calendar.readonly. Point the privacy/terms URLs at the pages your deployment serves under /legal/privacidad.html and /legal/condiciones.html. Both live in web/familynido-web/public/legal/ in the repo and are deployed automatically with the web image.

  3. In APIs & Services → Credentials, create an OAuth 2.0 Client ID of type Web application. Set the authorized redirect URI to exactly https://<your-host>/api/calendar/google/callback.

  4. Copy the client id and secret into .env:

    GOOGLE_CLIENT_ID=...
    GOOGLE_CLIENT_SECRET=...
    GOOGLE_OAUTH_REDIRECT_URI=https://<your-host>/api/calendar/google/callback
    
  5. Restart the API container so the new env vars are picked up:

    docker compose -f docker-compose.prod.yml up -d --force-recreate api
    

The callback path is already proxied by the existing /api/ block in deploy/nginx/default.conf, so no extra reverse-proxy config is needed.

Updating after a push to main

Until Watchtower is wired up (separate commit), updates are a manual pull:

cd /opt/familynido
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d

Rolling back

Each build tags the images with sha-<shortsha>. To roll back to a previous build without reverting the main branch:

# 1. Pin the images to a known-good SHA in docker-compose.prod.yml,
#    e.g. ghcr.io/.../familynido-api:sha-a57d52e
# 2. docker compose -f docker-compose.prod.yml up -d

Database migrations

Not yet automated. After a deploy that includes a new EF migration, run them manually from the repo on the dev machine pointed at the prod DB, or (TODO) add db.Database.Migrate() to Program.cs gated by config.