[AI] Fix Docker build for workspace:* dependencies (#7564)

* [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) <noreply@anthropic.com>

* [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) <noreply@anthropic.com>

* [AI] Rephrase 7564 release note to be user-facing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matiss Janis Aboltins
2026-04-22 20:20:51 +01:00
committed by GitHub
parent c3717e7036
commit 7501674613
4 changed files with 33 additions and 53 deletions

View File

@@ -1,8 +1,6 @@
node_modules
user-files
server-files
# Yarn
**/node_modules
.git
.lage
.pnp.*
.yarn/*
!.yarn/patches

View File

@@ -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 ./

View File

@@ -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 ./

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [MatissJanis]
---
Fix Docker container failing to start due to unresolved workspace dependencies.