Skip to content

Publish to ghcr.io and simplify setup to compose + .env - #5

Merged
dean1850 merged 3 commits into
mainfrom
claude/github-docker-ghcr-migration-x1x6pk
Jul 30, 2026
Merged

dean1850 merged 3 commits into
mainfrom
claude/github-docker-ghcr-migration-x1x6pk

Conversation

@dean1850

@dean1850 dean1850 commented Jul 30, 2026 •

Copy link
Copy Markdown
Owner

Installing jellylook meant cloning the repo and building the image locally, and the setup instructions were split between the README and a wall of # comments in .env.example — including a hashed-out Tautulli block users had to un-comment by hand.

A GitHub Actions workflow now builds and publishes a multi-arch image to ghcr.io/dean1850/jellylook, so installing is: download two files, fill in keys, docker compose up -d. Every comment is gone from .env.example and the explanations live in README tables instead.

Changes

  • .github/workflows/docker-publish.yml — builds an amd64 image, boots it, and checks /health, the UI, /api/status and /api/settings, asserts the container's own HEALTHCHECK reports healthy, and asserts startup fails fast on missing keys. Only then does it publish linux/amd64 + linux/arm64. Tags: latest on main, semver on v* tags, short sha, and the branch name.
  • docker-compose.yml pulls the published image. The local build path moves to docker-compose.build.yml for development. The ./data bind mount is unchanged, so existing installs keep their SQLite database.
  • Dockerfile — OCI labels (links the package to this repo), tzdata so TZ actually affects log timestamps, a HEALTHCHECK, and DATA_DIR. Still runs as root, matching the deliberate revert in Revert Dockerfile to run as root #2.
  • requirements.txt pinned so a given tag is reproducible.
  • .env.example — all comments and the hashed-out Tautulli block removed. All three history sources are now listed uncommented; HISTORY_SOURCE alone decides which are required, so nothing needs commenting in or out.
  • README.md — no-clone quick start, update instructions, a "building from source" section, and Configuration tables covering every variable, its default and where to get each key.
  • .dockerignore added.
  • RECS_PER_SCAN bug fix — see below.

Fixing RECS_PER_SCAN

Testing this migration surfaced a pre-existing bug: RECS_PER_SCAN in .env never did anything. init_db() seeded recs_per_scan=60 into app_settings, and _recs_per_scan() reads that table before falling back to .env, so the env var could not take effect on any install past its first run.

recs_per_scan is no longer seeded. A missing row means "use .env"; a row is written only when the user saves one in the Settings panel, so a deliberate panel choice still wins. Two supporting details:

  • Saving the panel at the .env value clears the row rather than storing it. The panel POSTs recs_per_scan on every save, so without this, saving any setting once would silently pin the batch size and make a later .env edit look ignored all over again.
  • GET /api/settings returns the effective value, so the panel shows the number a scan would really use instead of a hard-coded 60.

Existing databases already carry the seeded row, so a one-time migration drops it. A new schema_meta table records that the migration ran, so a value the user picks later is never deleted on a subsequent start.

Verification

Local end-to-end — 90/90 checks pass. The real FastAPI app was booted under uvicorn against stub Jellystat / Jellyfin / Tautulli / TMDb / OMDb / Seerr / LLM servers and driven over HTTP:

  • full scan in both jellystat and tautulli modes, end to end
  • already-watched titles excluded, TMDb-unresolvable titles dropped, post-enrichment dedupe
  • library ownership flagging via Jellyfin provider ids and via Plex title keys through Tautulli
  • metadata cache proven: a second scan made zero TMDb and zero OMDb calls
  • paging, sorting (match/imdb/year), type filters, the film alias, and a rejected bad filter
  • settings validation: clamping, 400 on a bad enum, no partial application, unknown keys ignored
  • every Seerr route including 409-as-already-requested and upstream-500-as-502
  • fail-fast startup: missing key exits non-zero and names the variable; bad LLM_PROVIDER rejected
  • RECS_PER_SCAN=13 yields a 13-title batch; a saved override of 20 wins; saving 13 again returns control to .env; a simulated pre-migration database picks up .env while keeping a later user choice across restarts
  • .env.example parses as a real .env, its values coerce to the right types, and an unfilled copy reports exactly the five keys the README says are required

In CI on this branch every smoke-test step passed and the multi-arch push succeeded. The published manifest is a clean OCI index:

linux/amd64  sha256:71765e51eab3d11d…
linux/arm64  sha256:687001046e60c638…

Confirmed pullable anonymously, so no registry login is needed. No unknown/unknown platform entries (provenance: false).

I could not build the image on my own machine — this environment's egress policy blocks Docker Hub's blob CDN — so CI is the authority on the image itself. Everything above the image layer was verified locally.

One correction testing forced

I had written a README warning that trailing # comments in .env break startup. I tested it: both Docker Compose and pydantic-settings strip them. The warning was wrong and was removed. Likewise, a note claiming forks' packages land private was removed — packages inherit the repo's visibility, verified with an anonymous pull.

Note

The workflow also triggers on claude/** branches, which is how the publish path was proven before merging. It means future branches publish a branch-tagged image (handy for testing, easy to drop — it's one line under on.push.branches). The claude-github-docker-ghcr-migration-x1x6pk tag can be deleted from the package once this merges.

dean1850 and others added 3 commits July 30, 2026 05:35
Installing jellylook required cloning the repo and building the image
locally. A GitHub Actions workflow now builds and publishes a multi-arch
image to ghcr.io/dean1850/jellylook, so setup is two downloaded files and
`docker compose up -d`.

- Add .github/workflows/docker-publish.yml. It builds an amd64 image,
  boots it and checks /health, the UI, /api/status and /api/settings,
  asserts the container reports itself healthy and that startup fails
  fast on missing keys, and only then publishes linux/amd64 +
  linux/arm64. Tags: latest on main, semver on v* tags, short sha, and
  the branch name.
- docker-compose.yml now pulls the published image; the local build path
  moves to docker-compose.build.yml for development.
- Dockerfile gains OCI labels, tzdata so TZ actually applies to log
  timestamps, a HEALTHCHECK, and DATA_DIR.
- Pin requirements so a given tag is reproducible.
- Add .dockerignore.
- Strip every comment and the hashed-out Tautulli block from
  .env.example; all three history sources are now listed uncommented and
  HISTORY_SOURCE alone selects which are required. The explanations move
  to README tables covering every variable, its default and where to get
  each key, alongside a no-clone quick start and update instructions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsEad8i4hYbwXXwAorPb6t
RECS_PER_SCAN in .env is overridden by the app_settings row that
init_db() seeds, so it only applies before the database exists — say so
rather than presenting it as the live batch size. Also stop listing
semver tags as available before any release is tagged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsEad8i4hYbwXXwAorPb6t
init_db() seeded recs_per_scan=60 into app_settings, and _recs_per_scan()
reads that table before falling back to .env, so RECS_PER_SCAN could
never take effect on any install past its first run.

recs_per_scan is no longer seeded. A missing row now means "use .env",
and a row is written only when the user saves one in the Settings panel,
so the panel still wins when it has been used deliberately. Saving the
panel at the .env value clears the row instead of storing it, so
routinely saving settings cannot silently pin the batch size and make a
later .env edit look ignored.

GET /api/settings returns the effective value rather than raw storage,
so the panel shows the number a scan would really use instead of a
hard-coded 60.

Existing databases carry the seeded row, so a one-time migration drops
it. A new schema_meta table records that it ran, so a value the user
chooses later is never deleted on a subsequent start.

Verified with an end-to-end suite driving the real app against stub
upstreams: RECS_PER_SCAN=13 yields a 13-title batch, a saved override of
20 wins, saving 13 again returns control to .env, and a simulated
pre-migration database picks up .env while keeping a later user choice
across restarts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsEad8i4hYbwXXwAorPb6t
@dean1850
dean1850 merged commit 683c429 into main Jul 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants