From 23ed3809cc111155e5b1dba4e387f3e8b62b630b Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Sat, 16 May 2026 17:54:00 -0400 Subject: [PATCH] speed up docker deploy builds --- .beads/issues.jsonl | 2 + deployment/docker/Dockerfile.ingest-options | 38 ++- deployment/docker/Dockerfile.service | 26 ++- deployment/docker/Dockerfile.web | 25 +- deployment/docker/README.md | 35 ++- ...26-05-16-1752-speed-up-docker-deploys.html | 219 ++++++++++++++++++ scripts/deploy.ts | 26 ++- 7 files changed, 349 insertions(+), 22 deletions(-) create mode 100644 docs/turns/2026-05-16-1752-speed-up-docker-deploys.html diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 605077e..1ac2304 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -11,6 +11,7 @@ {"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-09a","title":"Speed up Docker deployment builds","description":"Implement the Docker deployment optimization plan from /Users/kell/Desktop/speed-up-docker.md: split dependency installation from source copy, add BuildKit caches, make scoped deploys build only their target services, update Docker deployment docs, validate, document the turn, commit, and push.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:50:24Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:48Z","started_at":"2026-05-16T21:50:37Z","closed_at":"2026-05-16T21:53:48Z","close_reason":"Implemented Docker dependency-layer caching, scoped deploy build/up flow, Docker docs updates, validation, and turn documentation. Follow-up islandflow-cnk tracks daemon-backed image build verification.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-0sa","title":"Fix live tape auto-hold, history seam, and remove manual pause control","description":"The live tape should automatically hold when the user scrolls away from the top, resume when they return to the top or use Jump to top, and keep older prints available seamlessly beyond the hot window. Manual Pause/Resume control is now redundant and should be removed from live tape panes. This work should also fix the current regression where paused/held tapes still mutate, and align the options tape with a strict 100-row hot head backed by ClickHouse history.","notes":"Implemented live scroll-hold with no live pause button, demand-loaded ClickHouse history, a 100-row options hot head, and cache-first scoped snapshots. Validated with bun test apps/web/app/terminal.test.ts services/api/tests/live.test.ts and bun --cwd=apps/web run build.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T18:12:51Z","created_by":"dirtydishes","updated_at":"2026-05-16T18:23:43Z","started_at":"2026-05-16T18:12:54Z","closed_at":"2026-05-16T18:23:43Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-2db","title":"Manually remove stale islandflow local-infra containers from VPS","description":"The live VPS still has an older compose project named islandflow created from the repo-root docker-compose.yml. Inspection shows it is separate from the supported islandflow-vps deployment stack and exposes NATS, ClickHouse, and Redis on host ports. Container removal commands currently hang when run as the delta user through Docker, so cleanup likely needs a focused maintenance window and possibly host-level intervention or a Docker daemon restart.","notes":"The duplicate islandflow compose project on the VPS was confirmed live during inspection. Nginx Proxy Manager routes public traffic only to islandflow-vps web/api by Docker name, so the stale islandflow project appears to be stray local-infra state rather than part of the supported production path. Attempts to remove the stale containers with docker compose down and docker rm -f as the delta user hung and timed out, so manual cleanup likely needs a maintenance window and possibly Docker daemon intervention.","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:27:27Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:59Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-c87","title":"Clean up duplicate Islandflow Docker infra on VPS","description":"The live VPS is currently running both the production-style islandflow-vps Docker stack and an older root-level islandflow infra stack that publishes NATS, ClickHouse, and Redis on host ports. Investigate whether the older stack is unused, remove it safely if so, and update docs/deploy guidance so the server topology is clearer.","notes":"Inspected the live VPS and confirmed the duplicate compose project: islandflow-vps is the supported deployment stack, while a separate islandflow project from the repo-root docker-compose.yml still runs exposed NATS/ClickHouse/Redis containers. Verified Nginx Proxy Manager routes only to islandflow-vps web/api by Docker name. Attempted cleanup via docker compose down and docker rm -f on the stale islandflow containers, but those commands hung for the delta user and timed out. Added repo guardrails and docs so deploy warns when the duplicate project exists, and opened islandflow-2db for manual host-level cleanup during a maintenance window.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-16T01:16:05Z","created_by":"dirtydishes","updated_at":"2026-05-16T01:28:07Z","started_at":"2026-05-16T01:16:09Z","closed_at":"2026-05-16T01:28:07Z","close_reason":"Completed the repo-side investigation and guardrails. Actual server-side container removal is blocked by hanging Docker operations and is tracked separately in islandflow-2db for a maintenance window.","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -38,5 +39,6 @@ {"_type":"issue","id":"islandflow-zs0","title":"Migrate terminal UI to smart-money profiles","description":"Migrate apps/web terminal rendering to consume SmartMoneyEvent directly: primary profile, probability ladder, reason codes, and suppression/abstention state, while preserving legacy alert/classifier displays during the bridge.","status":"closed","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-04T21:35:23Z","created_by":"dirtydishes","updated_at":"2026-05-05T05:39:58Z","closed_at":"2026-05-05T05:39:58Z","close_reason":"Completed terminal smart-money profile migration","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-igk","title":"Add plan mode","description":"Implement a user-facing plan mode in the application so users can switch into planning before taking action. Scope to be clarified from existing app patterns.","status":"closed","priority":2,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-04T04:22:37Z","created_by":"dirtydishes","updated_at":"2026-05-04T04:26:18Z","started_at":"2026-05-04T04:22:40Z","closed_at":"2026-05-04T04:26:18Z","close_reason":"Implemented as a global pi extension toggled with Shift+P","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-biq","title":"Finish raw live options delivery and filter/backpressure observability","description":"The smart-money signal path and Tape filters are in place, but the next firehose pass should finish server-side selective raw live delivery for options subscriptions and add explicit filtered-out/backpressure observability for API/web counters. This was discovered while landing islandflow-e4r.\n","status":"in_progress","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:28:58Z","created_by":"dirtydishes","updated_at":"2026-04-29T03:54:12Z","started_at":"2026-04-29T03:54:12Z","dependencies":[{"issue_id":"islandflow-biq","depends_on_id":"islandflow-e4r","type":"discovered-from","created_at":"2026-04-28T16:28:58Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"islandflow-cnk","title":"Run Docker image build verification with active Docker daemon","description":"Targeted image builds could not run in the implementation session because the local Docker daemon was unavailable at unix:///Users/kell/.orbstack/run/docker.sock. When Docker or OrbStack is running, validate the refactored deployment Dockerfiles with: docker compose -f deployment/docker/docker-compose.yml build api; docker compose -f deployment/docker/docker-compose.yml build web; docker compose -f deployment/docker/docker-compose.yml build ingest-options.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-16T21:53:41Z","created_by":"dirtydishes","updated_at":"2026-05-16T21:53:41Z","dependencies":[{"issue_id":"islandflow-cnk","depends_on_id":"islandflow-09a","type":"discovered-from","created_at":"2026-05-16T17:53:40Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-38p","title":"Add native deployment unit templates and rollback helpers","description":"The deploy helper now supports --runtime native, but the repo still relies on operator-managed systemd units and manual rollback. Add checked-in native deployment templates or provisioning guidance for the expected units, and consider lightweight rollback/smoke-test helpers once the host-native path is exercised on the real VPS.","status":"open","priority":3,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-15T23:46:42Z","created_by":"dirtydishes","updated_at":"2026-05-15T23:46:42Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"islandflow-575","title":"Document smart-money event calendar env","description":"Document smart-money event-calendar environment configuration in env examples and README.\n","status":"closed","priority":3,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T06:57:14Z","created_by":"dirtydishes","updated_at":"2026-05-05T06:57:57Z","started_at":"2026-05-05T06:57:17Z","closed_at":"2026-05-05T06:57:57Z","close_reason":"Documented event-calendar env variables","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/deployment/docker/Dockerfile.ingest-options b/deployment/docker/Dockerfile.ingest-options index 156dc1d..52cba59 100644 --- a/deployment/docker/Dockerfile.ingest-options +++ b/deployment/docker/Dockerfile.ingest-options @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7 + FROM oven/bun:1.3.11 WORKDIR /app @@ -9,15 +11,39 @@ ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" COPY --from=workspace package.json ./package.json COPY --from=workspace bun.lock ./bun.lock COPY --from=workspace tsconfig.base.json ./tsconfig.base.json -COPY --from=services . ./services -COPY --from=packages . ./packages -COPY --from=apps . ./apps RUN apt-get update \ && apt-get install -y --no-install-recommends python3 python3-pip python3-venv \ && rm -rf /var/lib/apt/lists/* \ - && python3 -m venv "${VIRTUAL_ENV}" \ - && "${VIRTUAL_ENV}/bin/pip" install --no-cache-dir -r services/ingest-options/py/requirements.txt \ - && bun install --frozen-lockfile + && python3 -m venv "${VIRTUAL_ENV}" + +COPY --from=apps desktop/package.json ./apps/desktop/package.json +COPY --from=apps web/package.json ./apps/web/package.json + +COPY --from=packages bus/package.json ./packages/bus/package.json +COPY --from=packages config/package.json ./packages/config/package.json +COPY --from=packages observability/package.json ./packages/observability/package.json +COPY --from=packages storage/package.json ./packages/storage/package.json +COPY --from=packages types/package.json ./packages/types/package.json + +COPY --from=services api/package.json ./services/api/package.json +COPY --from=services candles/package.json ./services/candles/package.json +COPY --from=services compute/package.json ./services/compute/package.json +COPY --from=services eod-enricher/package.json ./services/eod-enricher/package.json +COPY --from=services ingest-equities/package.json ./services/ingest-equities/package.json +COPY --from=services ingest-options/package.json ./services/ingest-options/package.json +COPY --from=services ingest-options/py/requirements.txt ./services/ingest-options/py/requirements.txt +COPY --from=services refdata/package.json ./services/refdata/package.json +COPY --from=services replay/package.json ./services/replay/package.json + +RUN --mount=type=cache,target=/root/.cache/pip \ + "${VIRTUAL_ENV}/bin/pip" install -r services/ingest-options/py/requirements.txt + +RUN --mount=type=cache,target=/root/.bun/install/cache \ + bun install --frozen-lockfile + +COPY --from=services . ./services +COPY --from=packages . ./packages +COPY --from=apps . ./apps ENTRYPOINT ["bun"] diff --git a/deployment/docker/Dockerfile.service b/deployment/docker/Dockerfile.service index bc48d2d..e0fcf72 100644 --- a/deployment/docker/Dockerfile.service +++ b/deployment/docker/Dockerfile.service @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7 + FROM oven/bun:1.3.11 WORKDIR /app @@ -7,10 +9,30 @@ ENV NODE_ENV=production COPY --from=workspace package.json ./package.json COPY --from=workspace bun.lock ./bun.lock COPY --from=workspace tsconfig.base.json ./tsconfig.base.json + +COPY --from=apps desktop/package.json ./apps/desktop/package.json +COPY --from=apps web/package.json ./apps/web/package.json + +COPY --from=packages bus/package.json ./packages/bus/package.json +COPY --from=packages config/package.json ./packages/config/package.json +COPY --from=packages observability/package.json ./packages/observability/package.json +COPY --from=packages storage/package.json ./packages/storage/package.json +COPY --from=packages types/package.json ./packages/types/package.json + +COPY --from=services api/package.json ./services/api/package.json +COPY --from=services candles/package.json ./services/candles/package.json +COPY --from=services compute/package.json ./services/compute/package.json +COPY --from=services eod-enricher/package.json ./services/eod-enricher/package.json +COPY --from=services ingest-equities/package.json ./services/ingest-equities/package.json +COPY --from=services ingest-options/package.json ./services/ingest-options/package.json +COPY --from=services refdata/package.json ./services/refdata/package.json +COPY --from=services replay/package.json ./services/replay/package.json + +RUN --mount=type=cache,target=/root/.bun/install/cache \ + bun install --frozen-lockfile + COPY --from=services . ./services COPY --from=packages . ./packages COPY --from=apps . ./apps -RUN bun install --frozen-lockfile - ENTRYPOINT ["bun"] diff --git a/deployment/docker/Dockerfile.web b/deployment/docker/Dockerfile.web index 6956335..33723ae 100644 --- a/deployment/docker/Dockerfile.web +++ b/deployment/docker/Dockerfile.web @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7 + FROM oven/bun:1.3.11 AS build WORKDIR /app @@ -13,11 +15,32 @@ ENV NEXT_PUBLIC_NBBO_MAX_AGE_MS=${NEXT_PUBLIC_NBBO_MAX_AGE_MS} COPY --from=workspace package.json ./package.json COPY --from=workspace bun.lock ./bun.lock COPY --from=workspace tsconfig.base.json ./tsconfig.base.json + +COPY --from=apps desktop/package.json ./apps/desktop/package.json +COPY --from=apps web/package.json ./apps/web/package.json + +COPY --from=packages bus/package.json ./packages/bus/package.json +COPY --from=packages config/package.json ./packages/config/package.json +COPY --from=packages observability/package.json ./packages/observability/package.json +COPY --from=packages storage/package.json ./packages/storage/package.json +COPY --from=packages types/package.json ./packages/types/package.json + +COPY --from=services api/package.json ./services/api/package.json +COPY --from=services candles/package.json ./services/candles/package.json +COPY --from=services compute/package.json ./services/compute/package.json +COPY --from=services eod-enricher/package.json ./services/eod-enricher/package.json +COPY --from=services ingest-equities/package.json ./services/ingest-equities/package.json +COPY --from=services ingest-options/package.json ./services/ingest-options/package.json +COPY --from=services refdata/package.json ./services/refdata/package.json +COPY --from=services replay/package.json ./services/replay/package.json + +RUN --mount=type=cache,target=/root/.bun/install/cache \ + bun install --frozen-lockfile + COPY --from=services . ./services COPY --from=packages . ./packages COPY --from=apps . ./apps -RUN bun install --frozen-lockfile RUN bun run --cwd apps/web build FROM oven/bun:1.3.11 AS runtime diff --git a/deployment/docker/README.md b/deployment/docker/README.md index 7c4f03b..4a5019f 100644 --- a/deployment/docker/README.md +++ b/deployment/docker/README.md @@ -65,14 +65,16 @@ Important defaults: 3. Build and start the stack: ```bash -docker compose up -d --build +docker compose build api web compute candles ingest-options ingest-equities +docker compose up -d ``` If you are updating an existing deployment that already has failing `api` restart loops, do a full recreate so the ClickHouse config mount and dependency changes are applied cleanly: ```bash docker compose down -docker compose up -d --build --force-recreate +docker compose build api web compute candles ingest-options ingest-equities +docker compose up -d --force-recreate ``` 4. Confirm the containers are healthy: @@ -190,6 +192,19 @@ cd deployment/docker docker compose build web ``` +### Faster Docker builds + +The app images are structured so dependency installation is isolated from source code changes: + +- Docker first copies `package.json`, `bun.lock`, `tsconfig.base.json`, and workspace `package.json` files. +- `bun install --frozen-lockfile` runs in a cacheable layer with a BuildKit Bun cache mount. +- Source from `apps`, `services`, and `packages` is copied only after dependencies are installed. +- `ingest-options` also installs its Python sidecar dependencies from `services/ingest-options/py/requirements.txt` before source copy, using a BuildKit pip cache mount. + +That means normal TypeScript edits should reuse dependency layers. The first build after a fresh server checkout, Docker cache cleanup, dependency change, or Python requirement change can still be slow; later deploys should spend their time on changed source and the specific service images being rolled out. + +BuildKit cache mounts require a modern Docker Engine with Dockerfile frontend support. Docker Compose v2 on the VPS path enables this by default. + ## Safe rollouts on `152.53.80.229` The current live VPS uses Nginx Proxy Manager on the shared Docker network and routes public traffic to the Docker `web` and `api` containers by container name. Because of that, this Docker path remains the operationally correct default for the live server today. @@ -218,7 +233,7 @@ This flow: - checks the server checkout before switching anything - stops if the server has tracked local modifications - allows the known untracked tarball at `deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz` -- runs `git switch main`, `git pull --ff-only origin main`, and `docker compose up -d --build` +- runs `git switch main`, `git pull --ff-only origin main`, `docker compose build api web compute candles ingest-options ingest-equities`, and `docker compose up -d` - verifies the stack with `docker compose ps`, recent service logs, container-local health checks, and public HTTPS checks ### Deploy the current local branch @@ -253,6 +268,14 @@ Examples: ./deploy main --runtime docker --web-only --no-build ``` +Scoped Docker deploys now build only the selected image set and then restart only those services: + +- `--web-only`: `docker compose build web`, then `docker compose up -d web` +- `--api-only`: `docker compose build api`, then `docker compose up -d api` +- `--services-only`: builds and restarts `api`, `compute`, `candles`, `ingest-options`, and `ingest-equities` + +Use `--no-build` only when the image is already correct and you need Compose to recreate or restart containers, such as after changing server-side environment values that do not affect a Next.js build-time variable. Do not use `--no-build` for dependency changes, application source changes, or `NEXT_PUBLIC_*` changes. + ### Escalation path Use force recreate only when a normal refresh does not update the services cleanly: @@ -299,7 +322,8 @@ git switch main git pull --ff-only origin main cd /home/delta/islandflow/deployment/docker -docker compose up -d --build +docker compose build api web compute candles ingest-options ingest-equities +docker compose up -d ``` Deploy the current branch manually: @@ -314,7 +338,8 @@ git switch || git switch -c --track origin/ cd /home/delta/islandflow/deployment/docker -docker compose up -d --build +docker compose build api web compute candles ingest-options ingest-equities +docker compose up -d ``` If you changed only env values for the Bun services on the server: diff --git a/docs/turns/2026-05-16-1752-speed-up-docker-deploys.html b/docs/turns/2026-05-16-1752-speed-up-docker-deploys.html new file mode 100644 index 0000000..df16d62 --- /dev/null +++ b/docs/turns/2026-05-16-1752-speed-up-docker-deploys.html @@ -0,0 +1,219 @@ + + + + + + Speed Up Docker Deploys + + + +
+
+
2026-05-16 17:52 America/New_York
+

Speed Up Docker Deploys

+

+ Summary + Docker app images now cache dependency installation separately from source changes, and Docker rollouts now build only the images required by the selected deploy scope before restarting containers. +

+
+ +
+

Summary

+

+ Implemented the Docker deployment speed-up plan from /Users/kell/Desktop/speed-up-docker.md. The first build after this change may still be slow, but source-only changes should no longer invalidate the expensive Bun and Python dependency layers. +

+
+ +
+

Changes Made

+
    +
  • Refactored deployment/docker/Dockerfile.service to copy workspace manifests, run cached bun install --frozen-lockfile, then copy source.
  • +
  • Applied the same dependency-first build model to deployment/docker/Dockerfile.web, keeping the Next.js build after source copy.
  • +
  • Updated deployment/docker/Dockerfile.ingest-options with separate cached pip and Bun install layers before copying source.
  • +
  • Changed scripts/deploy.ts so Docker rollouts run explicit docker compose build <services> followed by docker compose up -d <services>.
  • +
  • Documented the faster-build model, scoped rollouts, and appropriate --no-build usage in deployment/docker/README.md.
  • +
+
+ +
+

Context

+

+ The previous Dockerfiles copied all app, service, and package source before dependency installation. That made nearly every code change invalidate bun install, increasing VPS deploy time. The deployment helper also used broad up -d --build behavior rather than a clean build phase scoped to the selected service set. +

+
+ +
+

Important Implementation Details

+

+ Each app image now copies root deployment manifests plus every workspace package.json before installing dependencies. The source tree is copied only after the install layer is complete. +

+
RUN --mount=type=cache,target=/root/.bun/install/cache \
+  bun install --frozen-lockfile
+

+ The ingest-options image also copies services/ingest-options/py/requirements.txt before source and uses a pip cache mount: +

+
RUN --mount=type=cache,target=/root/.cache/pip \
+  "${VIRTUAL_ENV}/bin/pip" install -r services/ingest-options/py/requirements.txt
+

+ For full Docker deploys, the helper builds the six core app services explicitly. For scoped deploys, it builds and restarts only the requested services. +

+
+ +
+

Expected Impact for End-Users

+

+ Users should see faster deployment turnaround after ordinary source edits because dependency installation is reused when manifests and locks have not changed. Scoped deploys should also disturb fewer containers, reducing restart surface for web-only, API-only, and backend-only updates. +

+
+ +
+

Validation

+
    +
  • Passed: bun run check:docker-workspace
  • +
  • Passed: ./deploy --help
  • +
  • Passed: docker compose -f deployment/docker/docker-compose.yml config --quiet with a temporary copy of .env.example
  • +
  • Passed: bun --cwd=apps/web run build
  • +
  • Passed: bun test with 222 passing tests
  • +
  • Not run: targeted Docker image builds because this session could not connect to the Docker daemon at unix:///Users/kell/.orbstack/run/docker.sock.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+

+ Docker daemon access was unavailable locally, so image builds still need to be exercised on a machine with a running Docker daemon or during the next VPS rollout. Static Compose validation and repo test coverage passed, and the Dockerfiles use standard BuildKit cache mounts supported by modern Docker Compose v2. +

+
+ +
+

Follow-up Work

+

+ No separate follow-up issue was created. The remaining verification is operational: run the targeted image builds once Docker or OrbStack is available. +

+
+
+ + diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 1ec3e6c..d6adcb1 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -324,6 +324,15 @@ function dockerServicesForScope(scope: DeployScope): string[] { } } +function dockerBuildServicesForScope(scope: DeployScope): string[] { + switch (scope) { + case "full": + return [...DOCKER_CORE_SERVICES]; + default: + return dockerServicesForScope(scope); + } +} + function dockerLogServicesForScope(scope: DeployScope): string[] { switch (scope) { case "web": @@ -565,15 +574,16 @@ function remoteDockerRollout( forceRecreate: boolean, noBuild: boolean ): void { - const services = dockerServicesForScope(scope); - const args = ["up", "-d"]; - if (!noBuild) { - args.push("--build"); - } + const rolloutServices = dockerServicesForScope(scope); + const upArgs = ["up", "-d"]; if (forceRecreate) { - args.push("--force-recreate"); + upArgs.push("--force-recreate"); } - const command = `docker compose ${[...args, ...services].join(" ")}`; + const buildServices = dockerBuildServicesForScope(scope); + const buildCommand = noBuild + ? null + : `docker compose build ${buildServices.join(" ")}`; + const upCommand = `docker compose ${[...upArgs, ...rolloutServices].join(" ")}`; runRemoteScript( "Remote Rollout", @@ -583,7 +593,7 @@ set -euo pipefail ${remoteGitUpdateScript(mode, branch)} cd ${shellEscape(REMOTE_DOCKER_DEPLOYMENT)} -${command} +${buildCommand ? `${buildCommand}\n` : ""}${upCommand} ` ); }