Skip to content

Make docker compose up the front door - #35

Merged
ARCoder181105 merged 6 commits into
mainfrom
clone-and-run
Aug 29, 2026
Merged

Make docker compose up the front door#35
ARCoder181105 merged 6 commits into
mainfrom
clone-and-run

Conversation

@ARCoder181105

@ARCoder181105 ARCoder181105 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added one-command local setup with generated secrets, database initialization, and startup guidance.
    • Added optional unauthenticated single-user mode for local development.
    • Added Dockerized web application serving with configurable API connectivity.
    • Added automatic database migrations during Compose startup.
  • Improvements

    • Services are now bound to localhost by default for safer development environments.
    • GitHub OAuth remains available when single-user mode is disabled.
    • Updated session information and interface behavior for single-user mode.
  • Documentation

    • Updated setup, development, configuration, limitations, and deployment guidance.

Both Dockerfiles build with `COPY . .` and there was no ignore file, so every
image carried the whole working tree -- including `.env`, which holds a real
GitHub client secret and the session secret. A secret copied into a layer
stays in that layer regardless of what the running container does with it.

This lands first and alone because it is the one fault that is actively
dangerous rather than merely broken: the other seven stop the stack from
working, this one would have shipped credentials.

Also keeps host `node_modules` out (wrong platform anyway -- the parser's
tree-sitter bindings are compiled), stale `dist` out, and test files out,
which incidentally closes the compiled-tests-in-dist gap CLAUDE.md records as
"harmless locally, wrong in an image".
Four faults in one commit, because they only fail one at a time.

The build context was `./apps/api` while the Dockerfile COPYs workspace
manifests and packages/shared from above it, so the build died on its first
COPY. It is the repo root now, with `dockerfile:` pointing here.

The runner never copied packages/shared and ran `node dist/index.js`, but
that package exports `./src/*.ts` and node cannot follow a `.ts` export. The
image ships shared as source and runs under tsx, which leaves `pnpm dev`,
Vite and Vitest resolving exactly as they did. `pnpm start` was `node
dist/index.js` and could never have worked either; it is `tsx src/index.ts`
now, closing a gap CLAUDE.md had recorded.

There was no worker service at all. A webhook verified its HMAC, enqueued a
job, answered 202, and nothing ever consumed it -- incremental refresh
silently did nothing, which reads as a slow parse rather than as a missing
process. It is the same image with a different command, since the two differ
only in entrypoint.

Three things found by building it rather than reading it:

pnpm 11.15.1 cannot run on Node 20. It uses a builtin the runtime does not
have and dies with ERR_UNKNOWN_BUILTIN_MODULE before installing anything, so
both stages are node:24-alpine.

The worker needs `git` in the image. `internal/clone/clone.go` shells out to
`git clone`, and a bare Alpine has none -- the existing parser image has the
same hole, which went unnoticed because the isolation harness only ever
parses a local fixture.

`.env` is written for host-native `make start`, where Postgres and Redis are
on localhost. Inside a container localhost is the container, so DATABASE_URL
and REDIS_URL are overridden with service names; `environment` beats
`env_file`. WEB_APP_URL, CORS_ORIGIN and GITHUB_REDIRECT_URI stay host-facing
because the browser is on the host.

The API port is published on 127.0.0.1 rather than 0.0.0.0. Verified running:
healthz answers 200, /api/repos answers 401, the worker attaches to the
queue, the parser binary and git are both present in the image, and no .env
was baked into a layer.
…ervice

Three of the remaining faults.

Nothing ran migrations in compose. `make start` uses the golang-migrate CLI,
which someone running `docker compose up` has no reason to have installed, so
the API and worker would have reached a database with no tables and failed in
a way that reads as a code bug. A `migrate` service on the image CI already
uses (R17), and both consumers gate on
`service_completed_successfully` -- finished, not merely started.

`apps/web` had no Dockerfile at all, so `docker compose build` failed here
first. It is a Vite build served by nginx, with `try_files` doing the SPA
fallback `vercel.json` does on Vercel -- without it a refresh on /app is a
404. Asset requests deliberately do not fall back: a missing bundle should
404 rather than quietly return the HTML shell, which is how a broken build
disguises itself as a routing bug. VITE_API_URL is a build ARG because Vite
inlines it; setting it on the container would do nothing.

The parser service declared `network_mode: none` and a dependency on Postgres
health, which cannot both hold -- with no interfaces it can never reach
Postgres over TCP. The dependency was vestigial and nobody hit it because
`make parser-isolated` runs it with `--no-deps`. It is gone; the three
isolation settings stay, since those are the actual contract. A `tools`
profile keeps `docker compose up` from starting a container the product never
uses (R38).

Two more found by building rather than reading. `tsconfig.json` extends
`@funcatlas/typescript-config/base.json`, so omitting that workspace package
made tsc fall back to its own defaults and report forty errors that all
traced to one missing file. And `pnpm run` verifies the workspace before
executing a script, so the build stage needs the lockfile and every manifest,
not just the ones the bundle imports -- otherwise it aborts with
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND attributed to the bundler.

Verified with the stack actually up: migrate applies and exits, / and /app
both answer 200, a missing asset answers 404, healthz answers 200, and the
parser container is not started.
Registering a GitHub OAuth app was the largest thing between a clone and a
running stack. FUNCATLAS_SINGLE_USER removes it: the API registers no
/auth/login, /auth/callback or /auth/logout and resolves every request to
that user.

The branch lives in `requireSession`, which is the only function that ever
sets `req.session` -- a second hook would be a parallel auth path, and those
drift until one forgets a check. The OAuth routes are not registered rather
than registered-and-refusing: 404 says nothing, 401 tells a prober there is
something here worth finding credentials for, which is the reasoning that
shaped R30.

This is not R30 in a new hat. That was a session-minting endpoint shipped in
every deployment behind a NODE_ENV gate, so the gate had to be right exactly
once. Here there is no endpoint and no gate: the instance is unauthenticated
because a human wrote their own username into their own .env.

What it is not is safe to expose, and I said otherwise when proposing it. The
process binds 0.0.0.0 -- it must, inside a container, or Docker cannot reach
it -- so an in-process loopback check is impossible. Compose publishes on
127.0.0.1, the server warns on every start, and R39 records the residual risk
instead of implying it is closed.

Three things this dragged in. The GitHub OAuth credentials had to become
conditionally required, or the API still refused to start without an app it
would never call; the check runs at startup, naming which are missing and
both ways to fix it, rather than surfacing as a 500 on first sign-in. The
arctic client is built lazily for the same reason. And a blank
FUNCATLAS_SINGLE_USER= now means off, because answering "I tried to turn this
off" with a Zod stack trace about string length is a bad first impression.

`make setup` writes .env, generates both secrets, creates the test database
and prints what it did -- including why the instance now has no
authentication. Idempotent, and it never overwrites an existing .env.

Two test faults fixed on the way. env.test classified any key without
`.default(` as required, so it demanded the newly optional ones in CI.
And a running compose worker consumes the jobs queue/parse.test.ts asserts
on, which reads as a broken queue rather than as two things sharing one
Redis; `make test` now says so and stops instead of failing obscurely.
Three faults, all found by cloning into /tmp and running the thing rather
than by reading it.

`.env.example` shipped `GITHUB_CLIENT_ID=<client-id>`, and the Makefile
sources that file with `set -a && . ./.env`. A shell reads `<` as a
redirection, so every env-using target died with "newline unexpected" the
moment `make setup` had copied it. The placeholders are empty values now,
which is also what the schema wants, and the guidance moved into comments.

`make setup` sourced .env to create the test database, which it does not
need: the credentials there are compose's own.

The one that actually mattered: `.optional()` only accepts a *missing*
variable, and an empty value is a present empty string, so `.min(1)` rejected
it. `GITHUB_CLIENT_ID=` -- the state a fresh .env is in, and the state anyone
leaves a key they are not using -- crashed the API on startup complaining
about the length of a credential single-user mode was never going to use. I
had already fixed exactly this for FUNCATLAS_SINGLE_USER and left the three
siblings broken, which is the whole argument for fixing at the shared point:
it is now one `blankAsUnset` helper wrapping all four.

Postgres and Redis published on 0.0.0.0 while everything else in this file
was bound to 127.0.0.1. On a stack whose API runs unauthenticated, an
internet-facing Postgres with the password `funcatlas` is the worse hole.
They are published at all only because `make start` runs the API natively.

Exit test passes from a clean clone with `make start` never run: `make setup`
then `docker compose up`, register sindresorhus/p-limit through the API with
no cookie, and the worker parses it to ready -- 6 files, 36 functions, commit
SHA recorded. The startup warning fires, /auth/me answers without a cookie,
and /auth/login is a 404.
The README told contributors `docker compose up` did not work and listed four
prerequisites. It is two commands now -- `make setup && docker compose up` --
with Docker, make and openssl the only things needed, and the native path kept
for anyone who wants hot reload.

The single-user warning is stated where it will be read: in the quickstart, in
"what it does not do", and in CLAUDE.md's known gaps. A README that buries
"this has no authentication" under a heading nobody scrolls to is not honest
just because the sentence exists somewhere.

R39 records the residual risk and says plainly that the mitigation is weaker
than the plan claimed -- the process binds 0.0.0.0 and cannot check what is in
front of it, so the protection is a loopback port map and a human writing the
value themselves, not a guard in code.

R11 said prod runs everything in compose. That was the intent from Phase 0 and
had never been true, which is how eight faults accumulated in a file nobody
ran; it is true now and says when it became so. PRD NFR-4 likewise.

Two CLAUDE.md gaps close with it: `pnpm start` could never run the API, and
compiled test files landed in `apps/api/dist`.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 3780c513-c291-4ee8-8674-c273c1ce3af7

📥 Commits

Reviewing files that changed from the base of the PR and between f179e55 and ef31ced.

📒 Files selected for processing (23)
  • .dockerignore
  • .env.example
  • CLAUDE.md
  • Makefile
  • PRD.md
  • README.md
  • TASKLIST.md
  • apps/api/Dockerfile
  • apps/api/package.json
  • apps/api/src/auth/github.ts
  • apps/api/src/auth/routes.test.ts
  • apps/api/src/auth/routes.ts
  • apps/api/src/auth/session.ts
  • apps/api/src/auth/single-user.test.ts
  • apps/api/src/env.test.ts
  • apps/api/src/env.ts
  • apps/web/Dockerfile
  • apps/web/nginx.conf
  • apps/web/src/App.test.tsx
  • apps/web/src/components/AppHeader.tsx
  • docker-compose.yml
  • docs/RISKS.md
  • packages/shared/src/types.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds a loopback-only Docker Compose runtime with migrations, API and worker containers, and nginx web serving. It adds make setup and optional unauthenticated single-user mode while preserving GitHub OAuth when configured.

Changes

Local runtime and authentication

Layer / File(s) Summary
Environment and session contracts
.env.example, apps/api/src/env.ts, apps/api/src/env.test.ts, packages/shared/src/types.ts
Blank values are treated as unset. OAuth settings become conditional when FUNCATLAS_SINGLE_USER is configured. SessionUser now includes singleUser.
Conditional authentication flow
apps/api/src/auth/*, apps/web/src/components/AppHeader.tsx, apps/web/src/App.test.tsx
Single-user mode creates synthetic sessions and omits OAuth routes. OAuth mode retains cookie and Redis authentication. The API response and web fixtures expose singleUser, and the header hides sign-out in single-user mode.
Container and Compose runtime
apps/api/Dockerfile, apps/api/package.json, apps/web/Dockerfile, apps/web/nginx.conf, docker-compose.yml, .dockerignore
The API and worker run TypeScript through tsx. Compose adds migrations, shared API and worker configuration, parser isolation, nginx serving, and loopback-only ports. Docker builds exclude secrets and generated files.
Setup workflow and operational records
Makefile, README.md, CLAUDE.md, PRD.md, TASKLIST.md, docs/RISKS.md
make setup creates local configuration, generates secrets, starts infrastructure, creates the test database, and prints startup instructions. Documentation records the workflow, single-user limitation, validation status, and discovered runtime issues.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to ef31c

The current changes introduce no identified merge-blocking correctness, security, availability, deployment, or runtime risk, so the PR is merge-ready after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant MakeSetup
  participant DockerCompose
  participant PostgreSQL
  participant Migrate
  participant API
  participant Worker
  participant Nginx
  Developer->>MakeSetup: Run make setup
  MakeSetup->>MakeSetup: Create .env and generate secrets
  MakeSetup->>DockerCompose: Start infrastructure
  DockerCompose->>PostgreSQL: Start and wait for health
  DockerCompose->>Migrate: Apply migrations
  Migrate-->>DockerCompose: Report migration success
  DockerCompose->>API: Start API
  DockerCompose->>Worker: Start worker
  DockerCompose->>Nginx: Serve built web assets
  Nginx-->>Developer: Expose local web application
Loading

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/api/package.json

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

apps/api/src/auth/github.ts

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).

apps/api/src/auth/routes.test.ts

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).

  • 8 others

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

@ARCoder181105
ARCoder181105 merged commit b1f1d09 into main Aug 29, 2026
4 checks passed
@ARCoder181105
ARCoder181105 deleted the clone-and-run branch August 29, 2026 18:58
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.

1 participant