Skip to content

feat(infra): one-command Docker Compose run, and fix the single-service image - #517

Merged
parthrohit22 merged 6 commits into
devfrom
chore/docker-compose
Oct 4, 2026
Merged

parthrohit22 merged 6 commits into
devfrom
chore/docker-compose

Conversation

@parthrohit22

Copy link
Copy Markdown
Collaborator

Summary

Adds docker-compose.yml so PARTHA runs with docker compose up --build, ahead of posting about the project (r/selfhosted readers will look for a Compose file first). Getting it to work in a real browser exposed two bugs in the #339 single-service image that made it unusable, both fixed here.

Linked issue

Related to #339 (single-service hosting) and #292. #292 withdrew the old multi-service Compose stack. This is a different, much smaller setup built on the existing root Dockerfile, added at the owner's request.

Scope

  • Issue or RFC this advances: feat(backend): serve the built frontend from FastAPI for single-service hosting #339. Its image didn't work in a browser until these fixes.
  • Why this is in scope: it fixes a broken existing path and adds a one-command local run. No new product surface.
  • Accepted evidence it is real: a clean docker compose up --build, plus a browser walkthrough (register → import pallets/click → analysis → Architecture → repo page refresh → Monaco preview).

What changed

Infra

  • docker-compose.yml: one service, SQLite and storage in the partha-data volume, APP_ENV=production, port bound to 127.0.0.1. AUTH_SECRET_KEY and AI_ENCRYPTION_KEY are generated on first start into /data/secrets, one file per setting, mode 600.
  • Dockerfile: creates /data owned by appuser, so a fresh named volume is writable. Also copies apps/backend/scripts so approve_email.py runs via docker compose exec. Render (/var/data) is unaffected.

Backend

  • config.py: new optional SECRETS_DIR, using pydantic-settings' secrets_dir (Docker-secrets style). Environment variables still take precedence.
  • Bug 1, blank page. The API's deny-all CSP was also applied to index.html, so every script and stylesheet was blocked. frontend_content_security_policy() now gives the shell its own policy:
    • inline scripts are allowed by a sha256 hash computed from the built file, with no unsafe-inline for scripts
    • Google Fonts and the jsDelivr-hosted Monaco editor are allowed
    • connect-src 'self' and frame-ancestors 'none'
    • API responses keep the deny-all policy
  • Bug 2, refresh gives a 401. /repositories, /repositories/:id and /analysis/:id/architecture are both client routes and API paths, so refreshing those pages returned the API's 401 JSON. SpaNavigationMiddleware sends browser page loads (Accept: text/html) to the shell. The exceptions are the API paths a browser really loads: docs, the OAuth redirects and the probes. API fetches are unchanged.

Docs

  • README: new "Or run it with Docker" section.
  • docs/DEVELOPMENT.md: no longer says there's no Compose file.

Acceptance criteria completed

  • docker compose up --build on a clean volume reaches /ready (production, database ok, storage ok)
  • The frontend renders, with no CSP violations after the fix
  • The first registration becomes owner (201). A second registration gets 422 until docker compose exec partha python scripts/approve_email.py, then 201
  • The session survives a page reload (the Secure refresh cookie works on http://localhost in Chromium)
  • GitHub import and analysis of pallets/click complete inside the container, and Architecture renders real modules
  • Refreshing /repositories/:id renders the app
  • Monaco loads from jsDelivr under the new CSP
  • Login still works after docker compose restart, so secrets persist

Testing performed

pytest (full backend suite, SQLite)          pass
pytest tests/test_auth.py tests/test_frontend_hosting.py tests/test_security_headers.py   52 passed
ruff check app tests / ruff format --check   clean
mypy app scripts                             no issues (154 files)
python scripts/check-capabilities.py         current
docker compose config -q                     ok
Manual: clean `docker compose down -v && up --build`, browser + curl walkthrough above (Colima, Docker 29.5)

Screenshots

Not applicable: no UI change. The fix makes the existing UI load in the container.

Security and data considerations

  • Generated secrets. They live only in the volume, mode 600, in a 700 directory, and are never logged. Losing the volume signs everyone out and makes saved provider keys unreadable; this is documented in the compose file.
  • Exposure. The port is bound to 127.0.0.1, consistent with "trusted-environment use".
  • CSP tradeoffs. The shell policy allows style-src 'unsafe-inline', because the graph and editor libraries inject styles. Scripts are hash-only.
  • Navigation middleware. It only rewrites the path for GET/HEAD requests whose Accept includes text/html. Every protected API route needs a bearer header, which a page load can't send, so no API response a browser could have read is lost.
  • Third-party requests. Google Fonts and the jsDelivr-hosted Monaco are fetched by the browser. That was already true of the frontend; it's worth deciding separately whether to self-host them for a "nothing leaves your machine" product.

Dependencies and blocked work

None.

Scope changes or remaining work

  • Not verified in Safari, which has historically been stricter about Secure cookies on http://localhost. If refresh logs you out there, an opt-out for the cookie's secure flag would be the follow-up.
  • Ollama-on-host AI is left as commented config, because the self_hosted egress CIDR depends on the Docker runtime.
  • No CI job runs the compose stack yet.

…ce image

Add docker-compose.yml: one container (the existing root Dockerfile) with
SQLite and storage in a named volume, APP_ENV=production, port bound to
127.0.0.1, and AUTH_SECRET_KEY / AI_ENCRYPTION_KEY generated on first start
into the volume (one file per setting, read via a new SECRETS_DIR setting so
`docker compose exec` scripts see the same keys).

Bringing it up exposed two bugs in the #339 single-service image that left
it unusable in a browser:

- The API's deny-all CSP was applied to the SPA shell, so every script and
  stylesheet was blocked and the page rendered blank. The shell now gets its
  own policy, with index.html's inline script allowed by a hash computed from
  the built file, plus Google Fonts and the jsDelivr-hosted Monaco editor.
- /repositories, /repositories/:id and /analysis/:id/architecture are both
  client routes and API paths, so a refresh on those pages returned the API's
  401 JSON. Browser page loads (Accept: text/html) now get the SPA shell,
  except the API paths a browser really loads (docs, OAuth redirects, probes).

The image also ships apps/backend/scripts so approve_email.py runs in the
container, and creates /data owned by appuser so a fresh volume is writable.
@vercel

vercel Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
partha-frontend Ready Ready Preview Oct 4, 2026 12:27pm UTC

Comment thread apps/backend/app/core/security_headers.py Fixed
- security_headers: collect inline scripts with html.parser instead of a
  lowercase-only regex (CodeQL "bad HTML filtering regexp"). Same hash for
  the real index.html; a test covers upper-case tags, attributes, and
  external scripts.
- brace-expansion 5.0.9 -> 5.0.12 and undici 7.29.0 -> 7.29.1 via the
  existing overrides (GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p,
  GHSA-rfgv-xxqx-mfg5, GHSA-w293-vg96-wgc3, all published 2026-09-29).
  Lockfile edited for just those two entries.
- Acknowledge GHSA-vfj7-8cjw-p6xm (braces): no patched release exists, it
  is a build-time-only transitive of tailwindcss, with a reachability guard
  and a 2026-12-31 review date.
# Conflicts:
#	apps/frontend/package-lock.json
#	apps/frontend/package.json
@parthrohit22
parthrohit22 merged commit df0e82b into dev Oct 4, 2026
15 checks passed
@parthrohit22
parthrohit22 deleted the chore/docker-compose branch October 4, 2026 13:23

This branch was successfully deployed

1 active deployment
Preview — d0e1ddb0 Deployed Oct 4, 2026 by vercel[bot]
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