ghcr.io/b3dmar/3ngram. It contains the MCP and
REST server, runs as the non-root node user, and includes the Apache-2.0
license and NOTICE. Postgres, pgvector, and Redis remain separate services; see
Self-host for the complete deployment path.
Pull a release
Versioned tags identify a release, whilelatest follows the newest stable
release. Use the published digest below when deployment immutability matters.
ghcr.io/b3dmar/3ngram:1.0.2.
Every release publishes a multi-platform OCI index for linux/amd64 and
linux/arm64. Docker selects the matching image automatically. To inspect or
select a platform explicitly:
1.0.0), major/minor (1.0), latest,
and sha-<full-git-sha>.
Pin an immutable digest
The release page records the exact multi-platform digest. Pulling by digest prevents a tag change from altering what is deployed:Verify provenance
The release workflow attaches a BuildKit SBOM and max-mode provenance to the OCI image. It also publishes GitHub-signed SLSA provenance for the image digest. With GitHub CLI authenticated to GitHub, verify both the repository and the exact signer workflow. Public pulls are anonymous; if your environment requires registry credentials, log in with a token that can read packages before running the command.Worker image
The background worker publishes its own image,ghcr.io/b3dmar/3ngram-worker,
alongside the server image on every release (multi-platform, same tag scheme:
full version, major/minor, latest, sha-<full-git-sha>). It is built from
apps/worker/Dockerfile
with the repo root as build context — the worker needs every workspace
dependency’s manifest, lockfile, and sources, not just its own directory. The
@3ngram/worker npm package itself stays private; the image is the only
published artifact.
EXPOSE, no PORT, and its HEALTHCHECK
is a process-liveness probe (node -e "process.exit(0)") rather than an
endpoint check. This is PID-only liveness: a missing REDIS_URL is a
hard boot failure, but a Redis outage after startup only logs reconnect
warnings while the retry loop spins — the process stays alive and the
container stays “healthy” with every job stalled. Monitor Redis and queue
throughput separately; the healthcheck cannot see them.
It validates the same production environment contract as the server
(packages/config/src/env.ts) minus PORT: BASE_URL (https) is still
required even though the worker serves no HTTP, along with REDIS_URL,
a DATABASE_URL whose username matches RUNTIME_DB_ROLE (default
app_user) and whose password is strong (12+ characters, not a shipped
dev/example value — weak passwords are rejected at boot), no
DATABASE_URL_UNPOOLED, and a non-placeholder LOG_HASH_SALT. OAUTH_JWKS is also required in production even though
the worker has no OAuth surface — the shared validation does not yet
distinguish the consuming process (tracked in issue #204); supply the
server’s value until then. SESSION_CLOSER_ENABLED (default false) gates the
session-sweep scheduler and closer job processors — with it off, the
scheduler is removed entirely and closer jobs no-op; consolidation,
surfacing, and OAuth-client GC run regardless.
The worker never runs migrations — there is no migrations stage in its
Dockerfile. The server image owns schema management, so self-host has exactly
one migrator and two deployables cannot race each other over the schema.