Mixly on Hugging Face Spaces β deploy & operations
The Space (jay-hank/mixly) is a Docker host. Its Dockerfile clones the
private GitHub repo phamdung2209/Mixly at build time and runs the Next.js app.
Public URL: https://jay-hank-mixly.hf.space
β οΈ Data isolation. Mixly lives under the
jay-hankHF account β a different account fromsolora-hq, which hosts Tierly and is LIVE with real merchant data. Mixly'sDATABASE_URL,S3_BUCKET,S3_ENDPOINT, and Space name are a completely separate set of identifiers from Tierly's β never copy a value from Tierly'sDEPLOY.mdhere. Concretely: DB path is/data/mixly.db(nottierly.db), bucket isMixly-storage(notTierly-storage) under thejay-hanknamespace (notsolora-hq), Space isjay-hank/mixly(notsolora-hq/Tierly).jay-hankhas no access tosolora-hqat all, so pointing Mixly at any of Tierly's identifiers won't just risk corrupting or leaking live merchant data β it will most likely just fail outright.
0. First-time setup (do this once β the Space already exists)
The Space jay-hank/mixly has already been created (Docker SDK) on the
jay-hank HF account. What's left:
- Add the remote and push this repo:
The first build will fail (or crash-loop) until the secrets in Β§2 are set β that's expected, HF just needs a repo to attach the build to.git remote add origin https://huggingface.co/spaces/jay-hank/mixly git push -u origin main - Create the S3 storage bucket: HF β your
jay-hankaccount β Storage β New bucket β name it exactlyMixly-storage(bare name, no slashes). Double check it is notTierly-storage. Read the risk note below first β Storage Buckets may not be available on this account's plan. - Set every secret in Β§2 below (Space β Settings β Variables and secrets), then Space β Settings β Factory rebuild.
π§ Risk: HF Storage Buckets may not be available on a free, non-Pro account.
jay-hankis a free, non-Pro account. This matters more than it sounds: the HF free tier has an ephemeral filesystem, so without a working S3 bucket the SQLite database is destroyed on every rebuild, restart, or wake-from-sleep β the app would silently lose every merchant's bundles, settings, and stats. Tierly has already suffered exactly this data-loss failure once, which is why the restore-on-boot mechanism (Β§4) exists. Before treating this deployment as production-ready, confirm that a bucket namedMixly-storagecan actually be created under thejay-hanknamespace and thatS3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEYwork againsthttps://s3.hf.co/jay-hank. If HF storage buckets aren't available on this plan, the options are: enable HF Persistent Storage (paid), upgrade the account, or point the backup at a different S3-compatible provider (src/lib/s3.tsspeaks plain S3, so any S3-compatible endpoint should work).
1. GitHub token (required β the repo is private)
Cloning a private repo inside the Docker build needs a token. Create a fine-grained Personal Access Token, scoped to just this repo, read-only:
- GitHub β Settings β Developer settings β Personal access tokens β Fine-grained tokens β Generate new.
- Resource owner:
phamdung2209. Repository access: Only select repositories βMixly. - Permissions β Repository β Contents: Read-only (that's all it needs).
- Expiration: 90 days (rotate on expiry). Generate β copy the
github_pat_β¦.
Then add it to the Space as a build secret:
- Space β Settings β Variables and secrets β New secret
- Name:
GITHUB_TOKENΒ· Value: thegithub_pat_β¦
The Dockerfile reads it via
--mount=type=secret,id=GITHUB_TOKEN, so it is never baked into an image layer.
2. Space secrets (runtime) β Settings β Variables and secrets
π Paste values in the HF UI, never into code/git. Secrets (hidden) vs Variables (visible) β put all credentials as Secrets.
Required
| Name | Value / where from |
|---|---|
GITHUB_TOKEN |
build secret from Β§1 |
SHOPIFY_API_KEY |
Partner Dashboard β Mixly β API credentials |
SHOPIFY_API_SECRET |
β³ (secret) |
NEXT_PUBLIC_SHOPIFY_API_KEY |
same value as SHOPIFY_API_KEY |
SHOPIFY_SCOPES |
read_orders,read_products,write_discounts (match shopify.app.toml) |
SHOPIFY_APP_URL |
https://jay-hank-mixly.hf.space |
SHOPIFY_API_VERSION |
2026-07 |
DATABASE_URL |
file:/data/mixly.db (persistent mount β see Β§3) |
SESSION_ENCRYPTION_KEY |
openssl rand -hex 32. Set once, never change (rotating it forces every store to re-auth). |
HF_SPACE_ID |
jay-hank/mixly β the in-code default (src/lib/ops/backup.ts) is still solora-hq/Mixly, which is wrong for this deployment, so this must be set explicitly. |
Ops Console + DB access (/ops, /ops/studio)
| Name | Value |
|---|---|
OPS_ALLOWLIST |
comma-separated emails allowed into /ops |
AUTH_SECRET |
npx auth secret. Keep stable (else your /ops sessions drop). |
AUTH_GOOGLE_ID |
Google Cloud Console β OAuth client ID |
AUTH_GOOGLE_SECRET |
β³ (redirect URI: https://jay-hank-mixly.hf.space/api/auth/callback/google) |
DB backup (Β§4) β HF Storage Bucket via S3 API
| Name | Value |
|---|---|
S3_ENDPOINT |
https://s3.hf.co/jay-hank (gateway scoped to the namespace) |
S3_BUCKET |
Mixly-storage (bare bucket name β not Tierly-storage) |
S3_ACCESS_KEY_ID |
HFAKβ¦ β HF Access Tokens β token (Write) β dropdown β Generate S3 credentials |
S3_SECRET_ACCESS_KEY |
β³ (shown once) |
HF_TOKEN |
HF bearer token β used to rebuild/restart the Space from /ops/backup after a DB restore (Β§4), and by scripts/sync-env.mjs (Β§5), NOT the S3 creds |
Optional (features degrade gracefully when unset)
SENTRY_DSN, GITHUB_REPO (overrides the clone source; the Dockerfile's
ARG GITHUB_REPO build arg already defaults to the right repo).
Mixly has no Crisp chat, QStash, or email (Resend) integration β unlike
Tierly, none of NEXT_PUBLIC_CRISP_WEBSITE_ID, QSTASH_*, RESEND_API_KEY,
EMAIL_FROM, or CRON_SECRET apply here; don't set them.
3. Persistent storage
Space β Settings β Persistent storage β enable (Small is plenty). It mounts
at /data, which is where DATABASE_URL=file:/data/mixly.db points. Without it,
/data resets on every rebuild β which is exactly why Β§4 exists.
Do not mount the Storage Bucket as a data volume. Backup talks to it over the S3 API (Β§4); a bucket mount is a schedule-time dependency and, if HF can't attach it, the Space fails with "Scheduling failure: unable to schedule".
4. Database backup / restore (HF Storage Bucket, S3 API)
The app backs the SQLite DB up to a Storage Bucket over the S3 API and restores
it on boot if /data is empty β so data survives a rebuild even on the free tier.
Network-only (no mount), so it never blocks Space scheduling.
- What's backed up: only the SQLite DB file (
/data/mixly.db). It holds everything stateful: Shopify sessions (tokens encrypted at rest), bundle offers, bundle configs, shop profiles, settings. Migrations live in git; nothing else needs backing up. - The one thing that is NOT in the bucket:
SESSION_ENCRYPTION_KEY. It lives only in Space secrets. Keep it β a restored DB with a lost/rotated key can't decrypt stored Shopify tokens (the app then just re-exchanges them, no data loss, but avoid churn). - Bucket:
jay-hank/Mixly-storage. Objects:latest.db(overwritten in place β no history growth) +snapshots/mixly-YYYY-MM-DD.db(daily, pruned after 30 days). - Schedule: in-process β an initial backup ~1 min after boot, then hourly, plus a
debounced write after each DB change and a final flush on SIGTERM (see
src/lib/backup.ts+src/lib/s3.ts, started fromsrc/instrumentation.ts). - Operator-driven backup/restore: unlike a plain snapshot loop, Mixly ships an
/ops/backuppanel (Google-authenticated, see Β§2) that can trigger an immediate backup, upload a snapshot to restore from, and inspect it (table/row checks) before committing. Restoring requires typingoverwriteto confirm, snapshots the current DB as apre-restore-β¦dbrollback key first, then callsrestartSpace()(needsHF_TOKEN) to restart the Space onto the restored file.
5. Deploy / rebuild
- Redeploy the web app: push to GitHub
main, then in the Space use Settings β Factory rebuild (re-clones latestmain). - Push local env vars as Space secrets:
HF_TOKEN=β¦ node scripts/sync-env.mjs .env.production.local jay-hank/mixlyβ readsKEY=valuelines and pushes each as a secret via the HF API. The script's built-in default Space is stillsolora-hq/Mixly(wrong for this deployment), so always passjay-hank/mixlyexplicitly as the second arg. Changes apply on the next rebuild/restart. - Deploy the Shopify Function / extensions: that is a separate target, run from
your machine (needs the Shopify CLI + Partner auth) β
npm run app:deploy. The container also runs this non-fatally during a Space build (see Dockerfile), gated on theSHOPIFY_CLI_PARTNERS_TOKENbuild secret.
After changing scopes or the app URL, re-open the app in the store to re-consent.