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
Architecture: reference table
Service / fileResponsibility
ComposeSeparate API/web images; read-only roots, dropped capabilities; publishes proxy only
APIOnly service with application secrets and ignored ./data host directory
ProxyDefault loopback listener; trusted HTTPS proxy needed for remote access

Install

  1. Generate private configuration:
npm run init:env
Install: reference table
Initializer outputSafety
APP_KEY, SETUP_TOKENIndependent random values
AUTOMATION_KEYRINGValid JSON containing one random 32-byte base64 AES key
.env / ./dataOwner-only configuration and private writable data directory
Existing setupNever overwritten; no generated secrets printed
No host Node.jsGetting started includes Docker-only initializer
  1. Validate and start:
docker compose config --quiet
docker compose up -d --build --wait
docker compose ps
  1. Open http://localhost:8888; use private .env SETUP_TOKEN to create first administrator. No default accounts; public registration stays off unless REGISTRATION_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_IMAGE to 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.

Configuration: reference table
VariablePurpose
APP_URLExact public browser origin, no path prefix
APP_KEYStable secret; no routine-upgrade rotation
SETUP_TOKENFirst-account setup only
AUTOMATION_KEYRINGAPI-only encrypted-credential keys
AUTOMATION_NETWORK_EXCEPTIONSOptional exact-origin network/HTTPS exceptions
SQL_ALLOWED_HOSTS, SQL_SQLITE_ROOTAllowed live-source host:port / existing external SQLite directory
SQL_CONNECTION_KEYStable ≥32-character SQL encryption secret; defaults to persistent APP_KEY
BIND_ADDRESS, HTTP_PORTProxy; defaults 127.0.0.1:8888
LANDING_ENABLEDOptional application landing; default false
REGISTRATION_ENABLEDPublic local-account registration
SMTP_URL, SMTP_FROMPassword recovery/email automation
STORAGE_DRIVER, S3_*, AWS_*Filesystem/S3-compatible attachments
OIDC_*, AI_*Optional sign-in / assistant
DB_CONNECTION, DATABASE_URLSQLite 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.

Automation Credential Keys And Network: reference table
Rotation stepAction
1Generate independent 32-byte key with unique ID
2Add key and change active in one edit; recreate API
3Replace/reconnect credentials when practical so new versions use active key
4Keep old keys while credential/OAuth-flow rows may reference them; back up full keyring/database and test restore before retirement
Automation Credential Keys And Network: reference table
Network policyBehavior
Private servicesRFC1918 / IPv6 ULA allowed
BlockedUnspecified, loopback, link-local, multicast, special mapped forms
Public credentialsHTTPS required
RedirectsRejected
ExceptionExact 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 postgres service.
  • External PostgreSQL: leave profile empty; set DATABASE_URL to 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
Live Table Databases: reference table
Source requirementConfiguration
External SQLiteExisting 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
ConcurrencySuitable journal mode, e.g. WAL on local storage
PostgreSQL/MySQLSELECT/UPDATE account; certificate-verified TLS default; private-source TLS disable only deliberate; Node trust for private CA
MySQL write-backTransactional engine such as InnoDB
Editable sourceText/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.

Customize The Landing Page: reference table
SettingAction
apps/web/src/landing.jsonEdit plain-text title, tagline, callToAction, footerNote; valid JSON, no HTML
BuildSource edits need rebuilt web image
GateOff by default; set LANDING_ENABLED=true, recreate API/web, re-enable in admin settings if previously disabled
Forced offEnvironment false overrides administrator setting
docker compose up -d --build web

HTTPS

  1. Put a trusted HTTPS reverse proxy before loopback Hopya.
  2. Set exact external APP_URL, e.g. https://tasks.example.com.
  3. Forward entire site including /api and /health, preserving Host/Origin.
  4. 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

Attachments: reference table
BackendSetup / recovery
Filesystem defaultSTORAGE_DRIVER=filesystem; private objects under ./data
S3-compatiblePrivate bucket, least privilege; S3_BUCKET, S3_REGION, optional S3_ENDPOINT, required AWS_*
Backend changeNot migration; transfer/verify objects first
Remote versionsIndependent lifecycle and backup policy

Password Recovery

Password Recovery: reference table
RequirementBehavior
SMTP_URL + SMTP_FROMVerified TLS relay, authorized sender
Reset link30 minutes; consume revokes sessions/API tokens
Provider validationTest 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_WRITES defaults 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

OIDC And AI: reference table
IntegrationBefore enabling
OIDCAuthorization Code, PKCE S256, client_secret_post; setup guide; auto-provision false unless deliberate verified non-admin enrollment
AIEmpty 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.

Backup And Restore: reference table
DeploymentRequired recovery material
SQLiteComplete ./data + encrypted .env
PostgreSQLConsistent database dump + .env; ./data for filesystem attachments
S3Objects/versions at same logical point as database
AutomationComplete 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

  1. Read release notes; take verified cold backup.
  2. Retain .env, especially application/keyring secrets. Older installs add keyring privately, never rerun initializer over existing setup.
  3. Update checkout; docker compose build --pull.
  4. docker compose up -d --wait.
  5. 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
Troubleshooting: reference table
CheckWhy
/healthAPI readiness
Disk, backup age, container health, TLS expiryOperator monitoring
Private diagnosticsNever 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.