Starring — a self-hosted app (GitHub stars/bookmarks manager) deployed as an all-in-one host-networked Nomad job: the Starring app plus a bundled PostgreSQL with a persistent volume. Set better_auth_url to your real domain and a stable better_auth_secret; serves plain HTTP, front it with a reverse proxy for TLS.
Needs nomad-pack on PATH. The script only adds the nomploy registry and runs this pack.
Source ↗ Project ↗ ⚑ Report an issue
Save as values.hcl, edit, then run:
# The name of the Nomad job.
job_name = "starring"
# The Nomad namespace to deploy into.
namespace = "default"
# The datacenters to deploy to.
datacenters = ["*"]
# The Starring app image. Pin a version/sha in production.
image = "ghcr.io/pipozzz/starring:latest"
# The PostgreSQL image for the bundled database (Starring targets PostgreSQL 17). Only used when bundled_db = true.
postgres_image = "postgres:17-alpine"
# Run a bundled PostgreSQL inside this job (true), or connect to an external / managed database via database_url (false). With false, no postgres/wait-db tasks and no db port are generated.
bundled_db = true
# External PostgreSQL connection string, used when bundled_db = false. e.g. postgres://user:pass@host:5432/starring.
database_url = ""
# Extra environment variables for the app task, as a name → value map. Use for SMTP (e.g. SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM) and any other app settings. Only add keys NOT already set by a dedicated variable above (duplicate env keys are an HCL error).
extra_env = {}
# Host port for the Starring web app (PORT).
port = 4321
# Host port for the co-located PostgreSQL.
db_port = 5432
# Password for the Starring PostgreSQL user. CHANGE THIS.
db_password = "starring"
# Public URL Starring is reachable at (BETTER_AUTH_URL). Empty = http://localhost:<port>. Set this to your real host/domain, e.g. https://app.example.com.
better_auth_url = ""
# Secret that signs auth sessions (BETTER_AUTH_SECRET). CHANGE THIS and keep it STABLE. Generate with: openssl rand -hex 32.
better_auth_secret = "change-me-openssl-rand-hex-32"
# Optional public site URL (SITE_URL). Empty = unset.
site_url = ""
# Allow open sign-ups (ALLOW_SIGNUP). Leave false and sign in / invite instead.
allow_signup = false
# Enable email/password auth (AUTH_PASSWORD) so you can create the first user.
password_auth = true
# Named volume for PostgreSQL data (/var/lib/postgresql/data). Holds all data — back it up.
db_data_volume = "starring_db_data"
# Placement constraints. Pin to the node holding the volume. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true".
constraints = []
# Resources for the Starring app task.
resources = {
cpu = 500
memory = 512
}
# Resources for the PostgreSQL task.
postgres_resources = {
cpu = 300
memory = 256
}
| Name | Type | Default | Description |
|---|---|---|---|
| job_name | string | "starring" | The name of the Nomad job. |
| namespace | string | "default" | The Nomad namespace to deploy into. |
| datacenters | list | ["*"] | The datacenters to deploy to. |
| image | string | "ghcr.io/pipozzz/starring:latest" | The Starring app image. Pin a version/sha in production. |
| postgres_image | string | "postgres:17-alpine" | The PostgreSQL image for the bundled database (Starring targets PostgreSQL 17). Only used when bundled_db = true. |
| bundled_db | bool | true | Run a bundled PostgreSQL inside this job (true), or connect to an external / managed database via database_url (false). With false, no postgres/wait-db tasks and no db port are generated. |
| database_url | string | "" | External PostgreSQL connection string, used when bundled_db = false. e.g. postgres://user:pass@host:5432/starring. |
| extra_env | map | {} | Extra environment variables for the app task, as a name → value map. Use for SMTP (e.g. SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM) and any other app settings. Only add keys NOT already set by a dedicated variable above (duplicate env keys are an HCL error). |
| port | number | 4321 | Host port for the Starring web app (PORT). |
| db_port | number | 5432 | Host port for the co-located PostgreSQL. |
| db_password key | string | "starring" | Password for the Starring PostgreSQL user. CHANGE THIS. |
| better_auth_url | string | "" | Public URL Starring is reachable at (BETTER_AUTH_URL). Empty = http://localhost:<port>. Set this to your real host/domain, e.g. https://app.example.com. |
| better_auth_secret set me | string | "change-me-openssl-rand-hex-32" | Secret that signs auth sessions (BETTER_AUTH_SECRET). CHANGE THIS and keep it STABLE. Generate with: openssl rand -hex 32. |
| site_url | string | "" | Optional public site URL (SITE_URL). Empty = unset. |
| allow_signup | bool | false | Allow open sign-ups (ALLOW_SIGNUP). Leave false and sign in / invite instead. |
| password_auth key | bool | true | Enable email/password auth (AUTH_PASSWORD) so you can create the first user. |
| db_data_volume | string | "starring_db_data" | Named volume for PostgreSQL data (/var/lib/postgresql/data). Holds all data — back it up. |
| constraints | list | [] | Placement constraints. Pin to the node holding the volume. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true". |
| resources | object | {
cpu = 500
memory = 512
} | Resources for the Starring app task. |
| postgres_resources | object | {
cpu = 300
memory = 256
} | Resources for the PostgreSQL task. |
No variables match.
This pack stores data in one Docker named volume:
starring_db_data
⚠ This pack bundles a database. A cold copy of the volume can be inconsistent — for a reliable backup, dump the DB (pg_dump / mysqldump) or stop the job while backing up.
restic
# Run on the node hosting this pack. Point restic at your repo first: # export RESTIC_REPOSITORY="s3:https://<account>.r2.cloudflarestorage.com/<bucket>" # export RESTIC_PASSWORD="<repo-password>" # export AWS_ACCESS_KEY_ID=<key> AWS_SECRET_ACCESS_KEY=<secret> restic backup \ /var/lib/docker/volumes/starring_db_data/_data
rclone (sync to S3/R2)
rclone sync /var/lib/docker/volumes/starring_db_data/_data backup:<bucket>/starring_db_data
Paths assume the default Docker volume location (/var/lib/docker/volumes). Restore by stopping the job, restoring files into the same volume, and re-running the pack.
Starring deployed as an all-in-one,
host-networked Nomad job: the Starring app plus a bundled PostgreSQL 17 with a
persistent volume. The app task is gated on the database accepting connections
(a prestart wait task), so the first migration doesn't race a cold database.
| Variable | Default | Description |
|---|---|---|
image |
ghcr.io/pipozzz/starring:latest |
App image (pin a version in production). |
postgres_image |
postgres:17-alpine |
Bundled PostgreSQL image (when bundled_db). |
bundled_db |
true |
Run a bundled PostgreSQL. Set false to use an external / managed DB via database_url. |
database_url |
"" |
External PostgreSQL connection string (when bundled_db = false). |
port |
4321 |
Host port for the web app. |
db_port |
5432 |
Host port for the co-located PostgreSQL. |
db_password |
starring |
Change this. PostgreSQL password. |
better_auth_url |
"" |
Public URL (BETTER_AUTH_URL). Empty → http://localhost:<port>. Set to your real domain. |
better_auth_secret |
change-me-… |
Change this, keep it stable. Signs auth sessions. openssl rand -hex 32. |
site_url |
"" |
Optional SITE_URL. |
allow_signup |
false |
Open sign-ups. |
password_auth |
true |
Email/password auth (create the first user). |
db_data_volume |
starring_db_data |
Named volume for PostgreSQL data — back it up. |
constraints |
[] |
Pin to the node holding the volume (see below). |
resources / postgres_resources |
— | Per-task CPU/memory. |
All data lives in the db_data_volume docker named volume, which is node-local.
Pin the job to the node that holds it so a reschedule always finds the data. On a
nomploy cluster:
constraints = [{
attribute = "${meta.nomploy_control_plane}"
operator = "="
value = "true"
}]
Serves plain HTTP — front it with Traefik / a reverse proxy for TLS, and set
better_auth_url to that public HTTPS URL.
nomad-pack run starring \
--var better_auth_url=https://app.example.com \
--var better_auth_secret=$(openssl rand -hex 32) \
--var db_password=$(openssl rand -hex 16)