Skip to content

Legacy: the old Docker install

This page is about the old Docker/microservice install. It is retired for new users and no longer installs out of the box: MinIO, which it depends on, is gone. The page is kept as it was for existing installs and is no longer updated.

New install: Install Jarvis Automation. Already running the old one: Migrating from the old install.

Pantry Store

The Pantry Store is the server-side API for the Jarvis Pantry — the package registry and submission pipeline for community-built commands. It manages submissions, dispatches test runs to GitHub Actions, verifies results via signed callbacks, and publishes approved packages.

Quick Reference

Deployment Fly.io (jarvis-pantry-store)
Source jarvis-pantry/
Framework FastAPI + SQLAlchemy
Database PostgreSQL (Fly managed)
Health endpoint GET /health
Self-hosting docker-compose.local.yaml (see Running Your Own Pantry)

Submission Pipeline

  1. Submit — Developer POSTs a manifest + package URL via the Pantry web UI or CLI
  2. Resolve — Pantry resolves a pip dependency lockfile for the submission's declared packages
  3. Dispatch — Pantry triggers a workflow_dispatch on jarvis-pantry-runner with submission_id, nonce, and the resolved lockfile as inputs
  4. Run — The runner's two-job workflow tests the submission in an isolated Docker sandbox
  5. Callback — Runner POSTs a signed result to /v1/submissions/{id}/container-result
  6. Verify — Pantry verifies the HMAC signature and updates submission state to published or rejected

Callback Security Model

Container test results are verified using HMAC-SHA256 over the raw request body, keyed by PANTRY_CALLBACK_SIGNING_KEY.

How It Works

The runner signs over {submission_id}|{nonce}|{request_body_bytes} and sends the digest in the X-Pantry-HMAC header. Pantry recomputes the digest from the raw request.body() bytes (not from a parsed JSON object) so the digest covers exactly the bytes that will be sent on the wire.

The nonce is a per-submission random value Pantry generates at dispatch time and stores in the database. It prevents replay attacks — a captured callback payload cannot be replayed against a different submission.

Startup Guard

When PANTRY_CONTAINER_RUNNER=github_actions, Pantry refuses to start if PANTRY_CALLBACK_SIGNING_KEY is unset. This prevents a misconfigured deploy from silently accepting unsigned callbacks.

Environment Variables

Variable Required Description
PANTRY_CALLBACK_SIGNING_KEY Yes (GHA runner mode) Shared HMAC secret. Must match the PANTRY_CALLBACK_SIGNING_KEY GHA env secret in the pantry-callback environment on jarvis-pantry-runner.
PANTRY_CONTAINER_RUNNER Yes Set to github_actions in production.

Key Rotation

See the Callback Signing Key Rotation runbook.

Database Notes

The submissions table uses a callback_nonce column (renamed from callback_token in jarvis-pantry#25 via alter_column — in-flight rows were preserved during the migration).

Lockfile Size Cap

The resolved dependency lockfile is shipped as a workflow_dispatch input. GitHub Actions has a ~64 KB ceiling on dispatch inputs, so Pantry enforces a 50 KB cap on resolved lockfiles and rejects submissions that exceed it.

Hashless lockfiles (pip-compile without --generate-hashes) are used since the hash-per-artifact format inflates lockfile size dramatically for packages with many wheel variants. Version pinning is preserved; only the wheel hashes are omitted.

Apt Package Allowlists

Submissions can declare apt_packages to request system package installation on the runner. Pantry validates these against allowlist YAML files that are bundled into the Docker image.

The config/ directory is copied into both Dockerfile and Dockerfile.fly via COPY config/ config/. It contains:

File Purpose
config/apt-allowlist.yaml Packages permitted for installation
config/apt-source-allowlist.yaml Apt sources permitted to be added
config/post-install-allowlist.yaml Post-install operations permitted

The allowlist check fails closed — if the config directory is absent from the image, any submission that declares apt_packages is rejected immediately:

Submission failed: apt allow-list config not found: /app/config/apt-allowlist.yaml

If you are building or rebuilding the Docker image and see this error, verify that COPY config/ config/ appears in the Dockerfile before the application entrypoint.

Forge Package Generation

POST /v1/forge/generate generates a Jarvis package from a natural-language description. It's BYOK — the caller supplies their own LLM API key (llm_api_key in the request body), used once and never stored.

Because the endpoint is unauthenticated and triggers LLM generation, it carries a per-client-IP rate limit — the tightest cap in the app:

Variable Default Description
FORGE_GENERATE_RATE_LIMIT_PER_HOUR 5 Per-IP cap on /v1/forge/generate requests per hour
RATE_LIMIT_DISABLED false Set true in dev to bypass all rate limits (submission and forge-generation alike)

Requests over the cap receive 429 Too Many Requests. The limit check runs before generation starts, so a rate-limited request never reaches the LLM.

Self-Hosting Features

Three opt-in features support a Pantry run on your own network. They are documented as a guide in Running Your Own Pantry; this section lists what the service exposes.

Variable Default Description
FORGE_LOCAL_LLM_ENABLED false Master switch for the local LLM features. Off, /v1/forge/local-llm* return 404 and no local model is listed.
FORGE_LOCAL_BASE_URL / FORGE_LOCAL_API_KEY / FORGE_LOCAL_MODEL — First-run defaults for the local server. Values saved through the API win.
FORGE_LOCAL_TIMEOUT / FORGE_LOCAL_MAX_TOKENS 900 / 8192 Read timeout in seconds and output cap for local models
PANTRY_UPSTREAM_URL — Public Pantry API to list next to local packages. Empty serves only what this Pantry holds.
PANTRY_UPSTREAM_TTL_SECONDS / PANTRY_UPSTREAM_TIMEOUT_SECONDS 300 / 8 Cache lifetime and request timeout for the mirror
PANTRY_GITHUB_TOKEN — Clone token for private repositories when adding a package. Never stored.
ADMIN_API_KEY — Required to add private packages and to change or probe the local LLM server; those endpoints refuse to run without it
Method Path Auth Purpose
GET /v1/forge/features none {local_llm, private_packages, upstream_mirror, admin_key_configured}
GET /v1/forge/local-llm none, or X-Admin-Key to see the address Saved settings: base_url, has_api_key, default_model, source. The key is never returned, and base_url is empty unless the request carries the admin key.
PUT /v1/forge/local-llm X-Admin-Key (required) Save base_url, api_key (omit to keep, "" to clear) and default_model
POST /v1/forge/local-llm/detect X-Admin-Key (required) List the models the server reports at /v1/models
POST /v1/admin/commands/from-repo X-Admin-Key (required) Publish a package from a Git repository, with no AI review or container test
DELETE /v1/admin/commands/{name}/local X-Admin-Key (required) Unpublish a package added from a repository

Settings that change at runtime (the local server's address, key and default model) are stored in the app_settings table. Everything else remains environment-only. With the mirror on, GET /v1/commands and /v1/categories merge local and public packages and each item carries "source". The field is present with or without the mirror: "local" for a package an administrator added from a repository, "pantry" for one published through this Pantry, "upstream" for a mirrored one. Local packages belong to one reserved author row (is_local, stored username _local) that no login can reach.

Database driver

SQLAlchemy 2.1 maps a bare postgresql:// URL to the psycopg v3 driver, which this project does not install. app/db.py and Alembic's env.py use postgresql+psycopg2:// instead.

Deployment

fly deploy -a jarvis-pantry-store

Alembic migrations run automatically on every container boot via the Fly CMD.

# Check health
curl https://jarvis-pantry-store.fly.dev/health