{"id":50447127,"url":"https://github.com/ddddami/relay","last_synced_at":"2026-05-31T22:03:36.642Z","repository":{"id":354085547,"uuid":"1221103221","full_name":"ddddami/relay","owner":"ddddami","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-27T03:27:50.000Z","size":166,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-27T05:11:35.398Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ddddami.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-25T18:56:44.000Z","updated_at":"2026-04-27T03:27:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ddddami/relay","commit_stats":null,"previous_names":["ddddami/relay"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ddddami/relay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddddami%2Frelay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddddami%2Frelay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddddami%2Frelay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddddami%2Frelay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ddddami","download_url":"https://codeload.github.com/ddddami/relay/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddddami%2Frelay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33750481,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T22:03:35.767Z","updated_at":"2026-05-31T22:03:36.638Z","avatar_url":"https://github.com/ddddami.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Relay\n\n**A deployment pipeline in one page.** Paste a public GitHub repository URL, build it with Railpack, run it as a container, and route it through Caddy from a single UI.\n\n\u003cimg width=\"3024\" height=\"1646\" alt=\"Screenshot 2026-04-27 at 03 47 37\" src=\"https://github.com/user-attachments/assets/f334f522-6d26-4db3-bf09-c607805a435e\" /\u003e\n\n\u003e Built for the Brimble take-home task. This is a scoped prototype, not a production-ready platform.\n\n## Quick Start\n\n### Option 1: Docker Compose\n\nRequirements:\n\n- Docker\n- Docker Compose\n\n```bash\ngit clone https://github.com/ddddami/relay\ncd relay\ndocker compose up\n```\n\nOpen `http://localhost`.\n\nIf you change workspace dependencies or Dockerfiles locally and want a fresh rebuild, use `docker compose up --build`.\n\n### Option 2: Mise\n\nIf you use [mise](https://mise.jdx.dev):\n\n```bash\nmise install\nmise run install\nmise run dev\n```\n\n## What It Does\n\n1. Accepts a public GitHub repository URL\n2. Persists a deployment record in SQLite\n3. Clones the repository inside the backend runtime\n4. Builds a runnable image with Railpack\n5. Starts a Docker container from that image\n6. Verifies the runtime and detects a reachable port\n7. Routes traffic through Caddy\n8. Streams logs live to the UI over SSE while persisting them to the database\n\nThe product surface stays intentionally small: one page, one API, one deployment pipeline.\n\n## Assignment Fit\n\nThis implementation is intentionally aligned to the take-home brief:\n\n- Single `docker compose up` boot path\n- One-page UI driving the full deployment flow\n- Real live log streaming via SSE\n- Real Railpack image builds\n- Docker runtime orchestration\n- Caddy as the single ingress point\n- Compact, maintainable code over feature breadth\n\nI kept the frontend to a single view and used TanStack Query for server state. I did not add TanStack Router because there is no multi-page surface in this prototype.\n\nOut of scope by design:\n\n- auth\n- orgs / multi-tenancy\n- billing\n- k8s\n- private repo support\n- production-grade scheduling and isolation\n\n## Stack\n\n- Frontend: React, Vite, TanStack Query\n- Backend: Fastify, TypeScript\n- Database: SQLite via Drizzle and `@libsql/client`\n- Build: Railpack\n- Runtime: Docker\n- Ingress: Caddy\n- Live logs: SSE\n\n## Some Notes on Architecture\n\n### Docker Socket Mounting\n\nThe API container mounts `/var/run/docker.sock` so it can build images and run deployment containers directly.\n\nWhy:\n\n- simplest way to demonstrate real container orchestration in the scope of this task\n- avoids Docker-in-Docker complexity for a local evaluator setup\n\nTrade-off:\n\n- weak isolation boundary\n- not something I would keep as-is in a production multi-tenant system\n\n### SQLite w/ Drizzle\n\nThe app uses SQLite for local persistence, accessed through Drizzle and `@libsql/client`.\n\nWhy:\n\n- one fewer moving part for reviewers\n- enough for a single-node local prototype\n- simple file-based persistence works well for this assignment\n\nTrade-off:\n\n- not appropriate for a larger multi-tenant control plane with higher write concurrency\n\n### SSE for Live Logs\n\nLogs are streamed with Server-Sent Events and also stored in the database.\n\nWhy:\n\n- fits the product need well because logs are server-to-client only\n- simpler than standing up a WebSocket channel for this use case\n- straightforward reconnect behavior\n\nTrade-off:\n\n- not bidirectional\n- less flexible if the UI later needs interactive terminal or streaming control messages\n\n### Subdomain-First Local Routing\n\nDeployments are exposed through `*.localhost` and proxied by Caddy into the API's deployment proxy.\n\nWhy:\n\n- path-based routing leaked framework internals for apps that expect root-relative assets\n- subdomain ingress is a better fit for real application hosting behavior\n- still works locally without extra DNS setup because `localhost` wildcard subdomains resolve on modern systems\n\nTrade-off:\n\n- slightly more complex than a pure `/apps/:id` route\n- there is still a compatibility `/apps/*` path route in the codebase, but it is no longer the primary model\n\n### Runtime Verification and Port Detection\n\nThe runner does not mark a deployment `running` immediately after `docker run`.\n\nCurrent behavior:\n\n- waits for the container to stabilize\n- probes common ports\n- persists the detected reachable port\n- fails the deployment if no reachable runtime is found\n\nWhy:\n\n- more honest platform behavior\n- avoids pretending a deploy succeeded when the process is dead or bound incorrectly\n\nTrade-off:\n\n- still heuristic-based\n- some apps with unusual runtime contracts may fail unless they bind a reachable HTTP port\n\n## Product Trade-Offs\n\nA few decisions were intentionally pragmatic for the task:\n\n- public GitHub repositories only (prevents complex auth stuff)\n- no queue system\n- no rollbacks\n- no auth\n- no dynamic Caddy API integration yet\n\nThat keeps the prototype focused on the actual deployment loop rather than platform perimeter work.\n\n## Project Structure\n\n```text\napps/\n  api/        Fastify API, DB access, deployment runner, SSE\n  web/        One-page React UI\npackages/\n  shared/     Shared deployment contracts\ninfra/\n  Caddyfile   Local ingress configuration\n```\n\n## Useful Commands\n\nFrom the repo root:\n\n```bash\npnpm install\npnpm format:check\npnpm lint\npnpm test:run\npnpm typecheck\n```\n\nTooling:\n\n- `oxfmt` for formatting\n- `oxlint` for linting\n- `vitest` for focused frontend behavior tests\n\nIf you change workspace dependencies while using the local Compose setup, the mounted dependency volumes may need a refresh:\n\n```bash\ndocker compose run --rm -e CI=true api pnpm install --frozen-lockfile\ndocker compose run --rm -e CI=true web pnpm install --frozen-lockfile\ndocker compose up -d\n```\n\nThe same recovery step is available through mise if you want it:\n\n```bash\nmise run compose-refresh-deps\n```\n\n## What I'd Change Next\n\n### First\n\n- replace the in-process background runner with a proper job queue (likely redis)\n- move build and runtime orchestration out of the API process boundary\n- drive Caddy dynamically through its API instead of relying on static local config\n\n### Then\n\n- make runtime contracts more explicit for deployed apps\n- replace blunt container teardown with more explicit lifecycle handling\n- improve failure summaries in the UI beyond raw logs\n- add image/cache reuse strategy for faster rebuilds\n- add private repo support through GitHub auth\n\n### Things I Would Not Keep As-Is\n\n- Docker socket mounting inside the API container\n- local-only dependency volume behavior in Compose\n- heuristic port probing as the only runtime contract\n\n## Hard Requirements Met\n\n- Runs end-to-end with `docker compose up`\n- Streams logs live to the UI via SSE\n- Uses Railpack to produce runnable images\n- Keeps the product surface to one UI and one API\n\n## Time Spent\n\nRoughly 2 to 3 days across planning, infra debugging, runtime behavior, UI wiring and screaming at my laptop.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fddddami%2Frelay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fddddami%2Frelay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fddddami%2Frelay/lists"}