From 750167461399deb2ca98980fc47c166f1c70e55e Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Wed, 22 Apr 2026 20:20:51 +0100 Subject: [PATCH] [AI] Fix Docker build for workspace:* dependencies (#7564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [AI] Fix Docker build for workspace:* dependencies Since @actual-app/crdt became a workspace:* dep, `yarn workspaces focus --production` creates relative symlinks in node_modules that dangle when only node_modules is copied into the prod image, breaking local Docker builds with ERR_MODULE_NOT_FOUND: @actual-app/crdt. Dereference yarn's workspace symlinks in the builder stage with `cp -RL` so the prod stage can copy a self-contained node_modules without needing to enumerate which workspace:* deps exist. Adding a new workspace:* dep now requires zero Dockerfile changes. Also move the sync-server .dockerignore to the repo root (and drop stray local node_modules / .git / .yarn caches from the build context), since docker builds use the repo root as context — the old sync-server-level file was no longer being applied. Fixes #7561. Co-Authored-By: Claude Opus 4.7 (1M context) * [AI] Strip dev-only dirs from dereferenced workspace packages The generic `cp -RL` step copies full workspace package trees into the image (src/, e2e/, tests, build-stats, etc.). Remove them after the dereference — they're not needed at runtime, and skipping them recovers ~67MB from the final image on both alpine and ubuntu variants. Co-Authored-By: Claude Opus 4.7 (1M context) * [AI] Rephrase 7564 release note to be user-facing Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .../.dockerignore => .dockerignore | 8 ++--- packages/sync-server/docker/alpine.Dockerfile | 36 +++++++------------ packages/sync-server/docker/ubuntu.Dockerfile | 36 +++++++------------ upcoming-release-notes/7564.md | 6 ++++ 4 files changed, 33 insertions(+), 53 deletions(-) rename packages/sync-server/.dockerignore => .dockerignore (66%) create mode 100644 upcoming-release-notes/7564.md diff --git a/packages/sync-server/.dockerignore b/.dockerignore similarity index 66% rename from packages/sync-server/.dockerignore rename to .dockerignore index 9a3b7d6774..137eb17307 100644 --- a/packages/sync-server/.dockerignore +++ b/.dockerignore @@ -1,8 +1,6 @@ -node_modules -user-files -server-files - -# Yarn +**/node_modules +.git +.lage .pnp.* .yarn/* !.yarn/patches diff --git a/packages/sync-server/docker/alpine.Dockerfile b/packages/sync-server/docker/alpine.Dockerfile index a0a1607656..0c8fa8e560 100644 --- a/packages/sync-server/docker/alpine.Dockerfile +++ b/packages/sync-server/docker/alpine.Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine AS deps +FROM node:22-alpine AS builder # Install required packages RUN apk add --no-cache python3 openssl build-base @@ -6,18 +6,9 @@ RUN corepack enable WORKDIR /app -# Copy only the files needed for installing dependencies COPY .yarn ./.yarn COPY yarn.lock package.json .yarnrc.yml ./ -COPY packages/api/package.json packages/api/package.json -COPY packages/component-library/package.json packages/component-library/package.json -COPY packages/crdt/package.json packages/crdt/package.json -COPY packages/desktop-client/package.json packages/desktop-client/package.json -COPY packages/desktop-electron/package.json packages/desktop-electron/package.json -COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json -COPY packages/loot-core/package.json packages/loot-core/package.json -COPY packages/sync-server/package.json packages/sync-server/package.json -COPY packages/plugins-service/package.json packages/plugins-service/package.json +COPY packages ./packages # Avoiding memory issues with ARMv7 RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi @@ -25,18 +16,15 @@ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; # Focus the workspaces in production mode RUN if [ "$(uname -m)" = "armv7l" ]; then npm_config_build_from_source=true yarn workspaces focus @actual-app/sync-server --production; else yarn workspaces focus @actual-app/sync-server --production; fi -FROM deps AS builder +# Dereference yarn's workspace:* symlinks so the prod stage can copy just node_modules. +RUN cp -RL node_modules node_modules.real \ + && rm -rf node_modules \ + && mv node_modules.real node_modules -WORKDIR /app - -COPY packages/sync-server ./packages/sync-server - -# Remove symbolic links for @actual-app/web and @actual-app/sync-server -RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server - -# Copy in the @actual-app/web artifacts manually, so we don't need the entire packages folder -COPY packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json -COPY packages/desktop-client/build ./node_modules/@actual-app/web/build +# Strip dev-only content from dereferenced workspace packages to keep the final image lean. +RUN find node_modules/@actual-app -maxdepth 2 -type d \ + \( -name src -o -name e2e -o -name __tests__ -o -name __mocks__ -o -name tests -o -name test -o -name build-stats \) \ + -exec rm -rf {} + FROM alpine:3.22 AS prod @@ -53,8 +41,8 @@ RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data WORKDIR /app ENV NODE_ENV=production -# Pull in only the necessary artifacts (built node_modules, server files, etc.) -COPY --from=builder /app/node_modules /app/node_modules +# sync-server entry flattened at /app so CMD stays `node app.js`. +COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/packages/sync-server/package.json ./ COPY --from=builder /app/packages/sync-server/build ./ diff --git a/packages/sync-server/docker/ubuntu.Dockerfile b/packages/sync-server/docker/ubuntu.Dockerfile index 74395ecc02..3bf9789230 100644 --- a/packages/sync-server/docker/ubuntu.Dockerfile +++ b/packages/sync-server/docker/ubuntu.Dockerfile @@ -1,22 +1,13 @@ -FROM node:22-bookworm AS deps +FROM node:22-bookworm AS builder # Install required packages RUN apt-get update && apt-get install -y openssl WORKDIR /app -# Copy only the files needed for installing dependencies COPY .yarn ./.yarn COPY yarn.lock package.json .yarnrc.yml ./ -COPY packages/api/package.json packages/api/package.json -COPY packages/component-library/package.json packages/component-library/package.json -COPY packages/crdt/package.json packages/crdt/package.json -COPY packages/desktop-client/package.json packages/desktop-client/package.json -COPY packages/desktop-electron/package.json packages/desktop-electron/package.json -COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json -COPY packages/loot-core/package.json packages/loot-core/package.json -COPY packages/sync-server/package.json packages/sync-server/package.json -COPY packages/plugins-service/package.json packages/plugins-service/package.json +COPY packages ./packages # Avoiding memory issues with ARMv7 RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi @@ -24,18 +15,15 @@ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; # Focus the workspaces in production mode RUN yarn workspaces focus @actual-app/sync-server --production -FROM deps AS builder +# Dereference yarn's workspace:* symlinks so the prod stage can copy just node_modules. +RUN cp -RL node_modules node_modules.real \ + && rm -rf node_modules \ + && mv node_modules.real node_modules -WORKDIR /app - -COPY packages/sync-server ./packages/sync-server - -# Remove symbolic links for @actual-app/web and @actual-app/sync-server -RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server - -# Copy in the @actual-app/web artifacts manually, so we don't need the entire packages folder -COPY packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json -COPY packages/desktop-client/build ./node_modules/@actual-app/web/build +# Strip dev-only content from dereferenced workspace packages to keep the final image lean. +RUN find node_modules/@actual-app -maxdepth 2 -type d \ + \( -name src -o -name e2e -o -name __tests__ -o -name __mocks__ -o -name tests -o -name test -o -name build-stats \) \ + -exec rm -rf {} + FROM node:22-bookworm-slim AS prod @@ -53,8 +41,8 @@ RUN groupadd --gid $USER_GID $USERNAME \ WORKDIR /app ENV NODE_ENV=production -# Pull in only the necessary artifacts (built node_modules, server files, etc.) -COPY --from=builder /app/node_modules /app/node_modules +# sync-server entry flattened at /app so CMD stays `node app.js`. +COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/packages/sync-server/package.json ./ COPY --from=builder /app/packages/sync-server/build ./ diff --git a/upcoming-release-notes/7564.md b/upcoming-release-notes/7564.md new file mode 100644 index 0000000000..9120018642 --- /dev/null +++ b/upcoming-release-notes/7564.md @@ -0,0 +1,6 @@ +--- +category: Bugfixes +authors: [MatissJanis] +--- + +Fix Docker container failing to start due to unresolved workspace dependencies.