This document covers how Afunana is built, deployed, updated, and backed up.
Update Workflow
Afunana runs on either Docker or Podman, auto-detected at install. Use whichever compose command is active on your host:
docker compose,podman-compose, ordocker-compose. The examples below usedocker composeas shorthand.
Updating a running Afunana instance takes two commands — pull the newer image(s) and bring the stack back up. In Caddy proxy mode, the caddy profile is included so the proxy is updated too.
Alternatively, re-run the setup script and select the update option:
When setup.sh detects an existing installation it offers a short menu:
| Option | Action |
|---|---|
| Update | Pull the newer image(s) and restart. Configuration, secrets, and data are preserved. |
| Reinstall | Re-run the full install against the existing directory. |
| Reconfigure domain | Change the public domain / TLS settings (Caddy mode) without a code change. |
Choose Update for a routine version bump. The script pulls the latest images and restarts all containers. Configuration, secrets, and collection data are preserved across updates.
Updating means pulling a new application image (from Docker Hub, an internal registry, or an offline tarball) and restarting the containers. The marketing site is bundled inside the application image, but a customer install does not serve it; the public afunana.io host serves only two things to a customer host — the one-line setup.sh and the hourly, non-blocking version-check.
Database schema migrations run automatically on startup. No manual SQL steps are needed.
Offline / Internal-Registry Updates
If the host has no Docker Hub access, update from the same sources used at install — either an internal registry mirror or an offline delivery tarball. Re-running setup.sh in the install directory will auto-load a tarball it finds there.
Reverse Proxy (Customer-Managed)
This section is the canonical reference for customer-managed reverse-proxy requirements. Other docs link here rather than restating the rules.
Enterprises that prefer their own reverse proxy (nginx, F5, HAProxy, Azure App Gateway) can run Afunana without the bundled Caddy. The customer's proxy must then replicate what Caddy did automatically. At a minimum the proxy must do four things, each of which, if missing, breaks the app in a specific way:
- Terminate TLS — present the customer's certificate and forward decrypted traffic to the app's HTTP port.
- Strip the
/apiprefix (most critical) — the frontend calls/api/...but the app's routes do not include/api; if the prefix is not stripped the app returns 405 or HTML instead of JSON. - Don't buffer SSE responses — chat answers stream token-by-token, so a buffering proxy makes chat look frozen; disable response buffering and allow a long read timeout.
- Forward the host and client headers — pass
Host,X-Forwarded-For, andX-Forwarded-Protoso the app builds correct URLs and CORS. Also allow large request bodies so attachment uploads are not rejected.
The app binds to 127.0.0.1:18080 when the proxy runs on the same host, or to all
interfaces (firewall-restricted) when the proxy is on a separate host. Set the
public URL the proxy serves as FRONTEND_URL.
The app now sends a SPA-friendly Content-Security-Policy (strict on /api, relaxed
on HTML/assets), so the proxy no longer needs to hide or replace the CSP header --
the only special handling required is the /api strip.
Network & Firewall Requirements
When running with a customer-managed proxy and/or an external database, allow the following egress and ingress paths:
| From | To | Port | Purpose |
|---|---|---|---|
| App host | SQL Server | 1433 |
Database connection |
| App host | LLM provider endpoint (e.g. Azure OpenAI) | 443 |
Model API calls |
| App host | Docker Hub / registry | 443 |
One-time image pull (skip if air-gapped/mirrored) |
| Users | Proxy | 443 |
User access (HTTPS) |
| Proxy | App | 18080 (e.g.) |
Proxy-to-app forwarding |
Version Management
Version File
Versions use a zero-padded 00.XX.XX scheme (e.g. 00.18.35) — never major.minor.patch JSON. The current application version and the latest-available version are tracked as 00.XX.XX strings (surfaced by the app's health check and its published version file).
Each deploy increments the patch segment automatically (00.18.34 → 00.18.35).
Update Checks
The application compares the running version against latest-version.json hosted on the Afunana static site. The Admin UI displays a notification when a newer version is available.
The update check fetches the latest version, compares, caches the result for one hour, and reports whether an update is available.
Developer Deploy Flow
The developer deployment pipeline runs from a Windows workstation through GitHub Actions to Docker Hub.
Step-by-Step
- Developer runs
deploy.ps1on Windows. - The script deletes any existing
.vsixfiles from thevscode/directory. - The script bumps the patch version in
version.json. - Changes are committed and pushed to the
masterbranch on GitHub. - GitHub Actions triggers the CI/CD pipeline.
The deploy script never touches customer servers. It only builds, pushes, and commits.
CI/CD Pipeline
The GitHub Actions workflow triggers on every push to master:
- Frontend build -- Runs
npm ciandnpm run buildin the client directory. - Docker build -- Built on Red Hat UBI 9 (
ubi9/python-312), split into a shared base image plus a thin application image. - Docker push -- Pushes to Docker Hub with two tags:
| Tag | Description |
|---|---|
latest |
Most recent build from master |
00.XX.XX |
Pinned version (e.g., 00.18.35) |
The Docker image is private. Pulling it requires a valid Docker Hub token.
Rollback
To roll back to a specific version, pin the image tag in docker-compose.yml to the desired 00.XX.XX tag and restart; to return to the latest build, set the tag back to latest and pull.
Database schema changes are forward-only and idempotent. Rolling back the application code does not undo schema changes, but new columns and tables are added safely without breaking older code.
Backup & Restore
Automated Daily Backups (bundled SQL Server)
In bundled-DB mode the installer configures an automated daily database backup via cron at 02:00 UTC (script /opt/afunana/scripts/backup-db.sh), retaining 7 days of .bak files in /opt/afunana/backups/. No manual action is required for routine database backups — the steps below are for ad-hoc snapshots or migrating to a new host.
TDE certificate vault — critical. With TDE encryption at rest enabled, a database backup is useless without its certificate. The installer backs up the TDE certificate and private key to
/opt/afunana/tde-cert-backup/(protected by thetde_cert_passwordsecret). Store this directory off-box in a secure vault. If you lose it, an encrypted database restore on a new server cannot be opened.External SQL Server. When
DB_MODE=external, database backups, encryption, and retention are the customer's DBA responsibility — Afunana does not back up a SQL Server it does not own. The items below still apply to the on-disk collection data, logs, and secrets.
What to Back Up
| Component | Location | Type |
|---|---|---|
| Database backups | /opt/afunana/backups/ |
Daily .bak files (bundled mode) |
| SQL Server data | Docker/Podman volume sqlserver_data |
Container volume (bundled mode) |
| TDE certificate | /opt/afunana/tde-cert-backup/ |
Files on disk (store off-box) |
| Collection data | /opt/afunana/backend/Data/ |
Files on disk |
| Application logs | /opt/afunana/Log/ |
Files on disk |
| Secrets | /opt/afunana/secrets/ |
Files on disk |
| Compose + env | /opt/afunana/docker-compose.yml, .env, backend/.env, backend/seed.env |
Files on disk |
Restoring
- Stop all containers.
- Restore the database volume from the backup (or restore a database backup file). On a new host, first restore the encryption (TDE) certificate before opening the encrypted database.
- Copy the collection data back into place.
- Copy the secrets back into place (and the encryption-certificate backup if relocating).
- Start the containers again.
Deploy Receiver (Legacy)
deploy_receiver.py is dead legacy code — it is not installed by setup.sh and is unused. Deploys to the project's own servers run via SSH-pull from GitHub Actions; customers update by pulling the image as described in Update Workflow.
Monitoring
Health Endpoint
Verifies the application process is running and the database connection is active. (Port 8001 is container-internal only; from the host use 18080.)
Container Logs
Each service streams its own log; the reverse-proxy and database logs are present only in the modes that run those services.
Container Status
The services for your modes should show status Up: always afunana-app; plus caddy (Caddy mode) and sqlserver (bundled-DB mode). init-frontend runs once and exits — that is expected.
Disk Cleanup
After updates, clean up old images and monitor disk usage.