Operate
Self-hosting and deployment
Deploy Hopya with Docker, configure HTTPS and storage, rehearse backups, and upgrade safely.
v0.3.0 referenceReviewed
On this page
Supported topology: one API replica. Use SQLite on local persistent storage or PostgreSQL. SQLite on NFS and multi-replica operation—even with PostgreSQL—are not supported.
Architecture
Browser -> 127.0.0.1:8888 -> Nginx -> Astro
-> AdonisJS -> SQLite /data or PostgreSQL
| Service / file | Responsibility |
|---|---|
| Compose | Separate API/web images; read-only roots, dropped capabilities; publishes proxy only |
| API | Only service with application secrets and ignored ./data host directory |
| Proxy | Default loopback listener; trusted HTTPS proxy needed for remote access |
Install
- Generate private configuration:
npm run init:env
| Initializer output | Safety |
|---|---|
APP_KEY, SETUP_TOKEN | Independent random values |
AUTOMATION_KEYRING | Valid JSON containing one random 32-byte base64 AES key |
.env / ./data | Owner-only configuration and private writable data directory |
| Existing setup | Never overwritten; no generated secrets printed |
| No host Node.js | Getting started includes Docker-only initializer |
- Validate and start:
docker compose config --quiet
docker compose up -d --build --wait
docker compose ps
- Open
http://localhost:8888; use private.envSETUP_TOKENto create first administrator. No default accounts; public registration stays off unlessREGISTRATION_ENABLED=true.
Use tagged prebuilt images instead of a source build
- Git tags publish
ghcr.io/<owner>/<repository>-api:<tag>and-web:<tag>, never ordinary branch pushes. - Set
HOPYA_API_IMAGE/HOPYA_WEB_IMAGEto immutable release tags, then:
docker compose pull api web
docker compose up -d --no-build --wait
Configuration
.env.example lists every Compose setting. Keep .env private and back it up encrypted.
| Variable | Purpose |
|---|---|
APP_URL | Exact public browser origin, no path prefix |
APP_KEY | Stable secret; no routine-upgrade rotation |
SETUP_TOKEN | First-account setup only |
AUTOMATION_KEYRING | API-only encrypted-credential keys |
AUTOMATION_NETWORK_EXCEPTIONS | Optional exact-origin network/HTTPS exceptions |
SQL_ALLOWED_HOSTS, SQL_SQLITE_ROOT | Allowed live-source host:port / existing external SQLite directory |
SQL_CONNECTION_KEY | Stable ≥32-character SQL encryption secret; defaults to persistent APP_KEY |
BIND_ADDRESS, HTTP_PORT | Proxy; defaults 127.0.0.1:8888 |
LANDING_ENABLED | Optional application landing; default false |
REGISTRATION_ENABLED | Public local-account registration |
SMTP_URL, SMTP_FROM | Password recovery/email automation |
STORAGE_DRIVER, S3_*, AWS_* | Filesystem/S3-compatible attachments |
OIDC_*, AI_* | Optional sign-in / assistant |
DB_CONNECTION, DATABASE_URL | SQLite default or PostgreSQL |
COMPOSE_PROFILES, POSTGRES_* | Optional bundled PostgreSQL |
After environment changes, recreate affected services:
docker compose up -d --force-recreate api web proxy
docker compose restart does not load changed environment values.
Automation Credential Keys And Network
{"active":"key-1","keys":{"key-1":"<32-byte-base64-key>"}}
Every value encodes exactly 32 random bytes. New initialization supplies it. On older installs, privately generate/add the complete one-line keyring and recreate API before credential/OAuth use. Never put it in web/UI/logs/Git. Missing keyring leaves tasks available but credential work explicitly unavailable.
Keep every key referenced by retained encrypted rows. Changing active key does not re-encrypt old versions. Losing the final key copy is unrecoverable from the encrypted database.
| Rotation step | Action |
|---|---|
| 1 | Generate independent 32-byte key with unique ID |
| 2 | Add key and change active in one edit; recreate API |
| 3 | Replace/reconnect credentials when practical so new versions use active key |
| 4 | Keep old keys while credential/OAuth-flow rows may reference them; back up full keyring/database and test restore before retirement |
| Network policy | Behavior |
|---|---|
| Private services | RFC1918 / IPv6 ULA allowed |
| Blocked | Unspecified, loopback, link-local, multicast, special mapped forms |
| Public credentials | HTTPS required |
| Redirects | Rejected |
| Exception | Exact scheme/host/port origin only, e.g. http://service.internal:8080,https://special.example; never relaxes credential path/origin binding |
Exceptions are high-trust: prefer network allowlists, never broad/user-controlled origins.
PostgreSQL
Initializer creates a private PostgreSQL password/URL but leaves SQLite selected. For a new bundled PostgreSQL deployment before first start:
COMPOSE_PROFILES=postgres
DB_CONNECTION=pg
- Bundled URL points to
postgresservice. - External PostgreSQL: leave profile empty; set
DATABASE_URLto external server. - Changing engines is data migration, not a variable switch that copies existing data.
Live Table Databases
Live sources are separate from Hopya’s own DB_CONNECTION. Enable only used destinations/files:
SQL_ALLOWED_HOSTS=postgres.internal:5432,mysql.internal:3306
SQL_SQLITE_ROOT=/data/external-tables
| Source requirement | Configuration |
|---|---|
| External SQLite | Existing files inside canonical root, not Hopya database; readable/writable by non-root API; no browser upload/default database |
| Standard mount | /data/external-tables → host ./data/external-tables; separate mount may use Compose override |
| Concurrency | Suitable journal mode, e.g. WAL on local storage |
| PostgreSQL/MySQL | SELECT/UPDATE account; certificate-verified TLS default; private-source TLS disable only deliberate; Node trust for private CA |
| MySQL write-back | Transactional engine such as InnoDB |
| Editable source | Text/numeric primary key, composite supported; views/keyless tables not live-editable |
Recreate API, then Import & export → Live SQL databases to connect. AES-256-GCM uses stable SQL_CONNECTION_KEY or APP_KEY; back it up with database. Changing key does not re-encrypt connections.
Back up source databases separately. Workspace export omits credentials/remote rows; per-Table CSV/JSON can export rows. Audit intent precedes remote write, but two-database commits are not atomic: reload/reconcile an interrupted write, never automatically replay it.
Customize The Landing Page
This is the application’s optional landing, separate from this documentation/marketing site.
| Setting | Action |
|---|---|
apps/web/src/landing.json | Edit plain-text title, tagline, callToAction, footerNote; valid JSON, no HTML |
| Build | Source edits need rebuilt web image |
| Gate | Off by default; set LANDING_ENABLED=true, recreate API/web, re-enable in admin settings if previously disabled |
| Forced off | Environment false overrides administrator setting |
docker compose up -d --build web
HTTPS
- Put a trusted HTTPS reverse proxy before loopback Hopya.
- Set exact external
APP_URL, e.g.https://tasks.example.com. - Forward entire site including
/apiand/health, preserving Host/Origin. - Configure inner real-IP trust for only the exact outer proxy address; inner Nginx replaces forwarded client-address headers before Adonis.
Never publish 3333/4321 or disable Origin checks. Secure cookies and mutation checks need accurate origin. Never trust all sources or broad/shared subnet forwarding.
Attachments
| Backend | Setup / recovery |
|---|---|
| Filesystem default | STORAGE_DRIVER=filesystem; private objects under ./data |
| S3-compatible | Private bucket, least privilege; S3_BUCKET, S3_REGION, optional S3_ENDPOINT, required AWS_* |
| Backend change | Not migration; transfer/verify objects first |
| Remote versions | Independent lifecycle and backup policy |
Password Recovery
| Requirement | Behavior |
|---|---|
SMTP_URL + SMTP_FROM | Verified TLS relay, authorized sender |
| Reset link | 30 minutes; consume revokes sessions/API tokens |
| Provider validation | Test delivery/sender/spam/expiry/recovery; no logged/displayed reset-token fallback |
MCP SSE proxy and write exposure
- SSE stays disabled until administrator enablement. Proxy preserves unbuffered streams and Authorization on GET/POST; HTTPS plus bounded connections/timeouts compatible with 30-minute sessions.
- No tokens in URLs/access logs. Compose
HOPYA_MCP_ALLOW_WRITESdefaults false; enable/recreate API only if every connected host enforces human approval per mutation. - SSE enablement is persisted admin setting, not environment override.
OIDC And AI
| Integration | Before enabling |
|---|---|
| OIDC | Authorization Code, PKCE S256, client_secret_post; setup guide; auto-provision false unless deliberate verified non-admin enrollment |
| AI | Empty AI_PROVIDER disables; review context disclosure/retention/billing/privacy; output is untrusted proposal, never direct mutation |
Backup And Restore
Workspace export is not a full backup. Retain original private .env and every key needed by old credentials.
| Deployment | Required recovery material |
|---|---|
| SQLite | Complete ./data + encrypted .env |
| PostgreSQL | Consistent database dump + .env; ./data for filesystem attachments |
| S3 | Objects/versions at same logical point as database |
| Automation | Complete referenced keyring, not just active key |
Cold SQLite backup
mkdir -p backups
chmod 700 backups
umask 077
docker compose stop proxy api
tar -czf backups/hopya-data.tgz -C data .
tar -tzf backups/hopya-data.tgz
sha256sum backups/hopya-data.tgz
docker compose up -d --wait
Store archive/encrypted configuration off-host. Verify full keyring presence without printing it.
For PostgreSQL, stop API writes and use supported pg_dump/pg_restore, not a data-volume archive. Verify dump before restart; coordinate filesystem/S3 object backup.
Restore without overwriting old data
docker compose stop proxy api
mkdir -p restore-data
chmod 700 restore-data
tar --no-same-owner -xzf backups/hopya-data.tgz -C restore-data
Create ignored compose.restore.yaml:
services:
api:
volumes:
- ./restore-data:/data
Validate/start with both files:
docker compose -f docker-compose.yml -f compose.restore.yaml config --quiet
docker compose -f docker-compose.yml -f compose.restore.yaml up -d --wait
Verify login, workspace counts, writes, permissions, private downloads before retiring old volume. Old backups can revive credentials: revoke affected access again before reopening.
Upgrades
- Read release notes; take verified cold backup.
- Retain
.env, especially application/keyring secrets. Older installs add keyring privately, never rerun initializer over existing setup. - Update checkout;
docker compose build --pull. docker compose up -d --wait.- Verify health/login/writes/attachments and configured credential decryption against a non-destructive destination.
Migration and compatibility notes
- Startup runs migrations automatically. Migration 0001 preserves linear versions and grants automation/credential management to former workspace managers: review roles.
- Existing hooks keep signing v1 until rotation; new/rotated hooks use HMAC v2.
- Keep selected database and object data in place. Removed pre-Lucid migration runner is not an upgrade path.
Troubleshooting
docker compose ps
docker compose logs --tail=100 api web proxy
| Check | Why |
|---|---|
/health | API readiness |
| Disk, backup age, container health, TLS expiry | Operator monitoring |
| Private diagnostics | Never post .env, cookies, Authorization, full Compose output, or raw provider errors |
Developers may use npm run test:deployment for an isolated Docker check with unique temporary resources, not operator .env. This documentation cleanup does not run that integration test.