Skip to content

Addons

Addons are backing services — databases, caches, object storage — that your app depends on. Hop3 manages their lifecycle (create, attach, detach, destroy) and injects the connection details into your app as environment variables on deploy.

This guide covers the four addons shipped in Hop3 0.5: postgres, mysql, redis, and s3.

Quick Start

The simplest way to use an addon is to declare it in hop3.toml. On first deploy, the addon is auto-provisioned; on subsequent deploys it's reused.

[metadata]
id = "my-django-app"

[run]
start = "gunicorn myapp.wsgi --bind 0.0.0.0:$PORT"

[[addons]]
type = "postgres"

[[addons]]
type = "redis"

After hop3 deploy, your app has DATABASE_URL and REDIS_URL in its environment — no further wiring needed.

CLI Workflow

When you need imperative control (multi-app sharing, ad-hoc provisioning, destruction), use the addon commands.

Create an addon

hop3 addon create postgres my-database
hop3 addon create redis my-cache
hop3 addon create mysql legacy-db
hop3 addon create s3 uploads

The addon name is free-form; pick one that describes the data, not the app. This keeps the name stable across apps that share the addon.

Attach to an app

hop3 addon attach my-database --app my-app
hop3 addon attach my-cache --app my-app

Attach injects the addon's env vars into the app on the next deploy or restart. An addon can be attached to multiple apps (shared database pattern).

Detach and destroy

hop3 addon detach my-database --app my-app     # Remove env vars; keep data
hop3 addon destroy my-database                 # Permanently delete (prompts)

destroy requires typed confirmation of the addon name. Pass --confirm=<name> to skip the interactive prompt in scripts. --force skips all safety checks.

List and inspect

hop3 addon list                        # All available addon types (alias: addons)
hop3 addon show my-database            # Full details for one addon
hop3 addon status my-database          # Health and connection check

Addon Reference

postgres

Provisions a PostgreSQL database owned by a per-app user with CREATE ON DATABASE + CREATE, USAGE ON SCHEMA public grants, so migrations can install trusted extensions (pg_trgm, uuid-ossp, citext) without superuser help.

Injected env vars:

Variable Example
DATABASE_URL postgres://myapp:pass@localhost:5432/myapp
PGDATABASE myapp
PGUSER myapp
PGPASSWORD (generated)
PGHOST localhost
PGPORT 5432

Non-trusted extensions (e.g. postgis, pgvector, bloom) cannot be installed by the per-app user. Declare them in hop3.toml and Hop3 will install them as superuser at provisioning time:

[[addons]]
type = "postgres"
extensions = ["postgis", "pgvector"]

Hop3 enforces an allow-list. The default set covers the PG13+ trusted extensions (pg_trgm, hstore, citext, pgcrypto, uuid-ossp, …) plus widely-used non-trusted ones audited to carry no privilege-escalation surface (postgis, pgvector, bloom, cube, earthdistance, ip4r, pg_stat_statements). To enable an additional extension on a specific Hop3 install — e.g. pg_partman or a vendor extension — set the operator-side env var HOP3_EXTRA_PG_EXTENSIONS (comma-separated). A small hard-deny set (postgres_fdw, dblink, file_fdw, adminpack, untrusted PL languages) cannot be enabled even via the override; those grant filesystem / network / arbitrary-code-execution capability and would defeat the separation between "deploy an app" and "execute as the postgres superuser".

Some extensions (pg_cron, timescaledb) require shared_preload_libraries and a Postgres restart on top of CREATE EXTENSION. Hop3's default installer does not yet pre-load arbitrary extensions; treat those as a separate setup step.

mysql

Injected env vars:

Variable Example
DATABASE_URL mysql://myapp:pass@localhost:3306/myapp
MYSQL_DATABASE myapp
MYSQL_USER myapp
MYSQL_PASSWORD (generated)
MYSQL_HOST localhost
MYSQL_PORT 3306

Docker apps: Hop3 grants MySQL access on multiple host patterns (@'localhost', @'127.0.0.1', @'172.%') so apps reaching the host MySQL via the Docker bridge authenticate correctly. You don't need to think about this — it's automatic — but it's worth knowing if you inspect the mysql.user table.

redis

A logical Redis database (numeric, 0–15 by default) is allocated per addon instance; multiple addons can coexist on the same Redis server.

Injected env vars:

Variable Example
REDIS_URL redis://127.0.0.1:6379/2
REDIS_HOST 127.0.0.1
REDIS_PORT 6379
REDIS_DB 2

Note: REDIS_HOST is 127.0.0.1 (not localhost) to avoid IPv6 resolution issues. For Docker apps, the Docker deployer rewrites 127.0.0.1 → host.docker.internal at deploy time.

s3

Provisions a bucket (named hop3-<addon-name>) and a scoped access key on the configured S3 backend (MinIO in 0.5; Garage planned for 0.6).

Injected env vars:

Variable Example
S3_ENDPOINT http://localhost:9000
S3_BUCKET hop3-uploads
S3_ACCESS_KEY (generated)
S3_SECRET_KEY (generated)
S3_REGION us-east-1
S3_USE_PATH_STYLE true

Path-style URLs are required for MinIO; virtual-host style will arrive with the Garage backend.

Common Patterns

Mapping to app-specific variable names

If your app expects variables like DB_HOST or CACHE_URL rather than the canonical Hop3 names, remap in [env]:

[env]
DB_HOST = "${PGHOST}"
DB_PORT = "${PGPORT}"
DB_NAME = "${PGDATABASE}"
DB_USER = "${PGUSER}"
DB_PASS = "${PGPASSWORD}"
CACHE_URL = "${REDIS_URL}"

Or — for apps that translate only at runtime (e.g. Django reading POSTGRES_USER) — wrap the start command:

[run]
start = "env POSTGRES_USER=$PGUSER POSTGRES_PASSWORD=$PGPASSWORD POSTGRES_DB=$PGDATABASE POSTGRES_HOST=$PGHOST POSTGRES_PORT=$PGPORT gunicorn myapp.wsgi:application --bind 0.0.0.0:$PORT"

Sharing an addon across apps

hop3 addon create postgres shared-db
hop3 addon attach shared-db --app api
hop3 addon attach shared-db --app worker
hop3 addon attach shared-db --app admin

Each app receives the same DATABASE_URL and can coordinate via the shared database.

Multiple addons of the same type

Declare one [[addons]] block per instance:

[[addons]]
type = "redis"
name = "session-store"

[[addons]]
type = "redis"
name = "job-queue"

Env-var prefixing for distinct instances is planned for 0.6; in 0.5, only the first attached instance's variables (REDIS_URL, etc.) are injected.

Backup and Restore

Addons are included in app-level backups by default. See Backup & Restore.

hop3 backup create --app my-app           # Includes attached addon data
hop3 backup create --app my-app --no-addons   # App code + env only

Per-addon backup commands (addon backup create <name>, addon backup restore) are planned for 0.6.

See Also