# syntax=docker/dockerfile:1.7 # ============================================================================= # AFFiNE all-in-one Docker build (self-contained, no prebuilt artifacts needed) # # Stages: # 1. frontend - builds @affine/web + @affine/admin + @affine/mobile dists # 2. native - cross-compiles @affine/server-native (Rust) per TARGETARCH # 3. server-dist - bundles @affine/server (rspack) -> dist/main.js # 4. deps - installs production node_modules (multi-arch aware) # 5. runtime - assembles final image, mirrors .github/deployment/node/Dockerfile # # Build: docker buildx build --platform linux/amd64,linux/arm64 -f Dockerfile.all-in-one . # ============================================================================= # --- Shared base args --------------------------------------------------------- FROM node:22-bookworm-slim AS base RUN apt-get update && \ apt-get install -y --no-install-recommends jq python3 make g++ && \ rm -rf /var/lib/apt/lists/* WORKDIR /app # Copy everything the yarn install / builds need (respects .dockerignore). COPY . /app/ # Yarn 4 via corepack; keep scripts off like CI (.yarnrc.yml enableScripts=false) RUN corepack enable && corepack prepare yarn@4.18.0 --activate # Full dependency install for build stages. # Prisma postinstall generates client for the BUILD platform only; the runtime # stage re-generates it for the target platform (see `deps` stage). RUN yarn install --immutable # ============================================================================= # Stage 1: frontend dists (web + admin + mobile) # ============================================================================= FROM base AS frontend # Self-hosted bundle: force publicPath '/' so assets are served from this # container instead of affineassets CDN (html-plugin getPublicPath()). ENV BUILD_TYPE=stable \ PUBLIC_PATH=/ \ NODE_ENV=production \ GITHUB_SHA=dockerselfhost RUN yarn affine @affine/web build && \ yarn affine @affine/admin build && \ yarn affine @affine/mobile build # ============================================================================= # Stage 2: Rust native module (@affine/server-native), per target arch # ============================================================================= FROM base AS native ARG TARGETARCH # Rust toolchain (minimal profile; extra targets added per-arch below). RUN apt-get update && \ apt-get install -y --no-install-recommends curl ca-certificates && \ rm -rf /var/lib/apt/lists/* ENV RUSTUP_HOME=/usr/local/rustup \ CARGO_HOME=/usr/local/cargo \ PATH=/usr/local/cargo/bin:${PATH} RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.97.1 --profile minimal && \ cargo --version && rustc --version ENV CARGO_PROFILE_RELEASE_LTO=thin \ CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 \ CARGO_PROFILE_RELEASE_OPT_LEVEL=3 \ CARGO_PROFILE_RELEASE_STRIP=symbols WORKDIR /app/packages/backend/native # Cross-compile with Debian's cross-GCC instead of napi-rs' bundled GCC 4.8.5: # gcc 4.8.5 does not define __ARM_ARCH for aarch64, breaking aws-lc-sys asm. RUN if [ "$TARGETARCH" = "arm64" ]; then \ apt-get update && \ apt-get install -y --no-install-recommends \ gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross && \ rm -rf /var/lib/apt/lists/* && \ rustup target add aarch64-unknown-linux-gnu && \ CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \ CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \ AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ yarn build --target aarch64-unknown-linux-gnu && \ # index.js resolves arch-specific names; rspack bundles all of them. cp server-native.node server-native.arm64.node && \ cp server-native.node server-native.x64.node && \ cp server-native.node server-native.armv7.node; \ else \ yarn build --target x86_64-unknown-linux-gnu && \ # Only server-native.node is present from napi; provide arch aliases so # rspack can resolve index.js's arch-specific requires (docker-clean # prunes the wrong-arch binary at runtime anyway). cp server-native.node server-native.x64.node && \ cp server-native.node server-native.arm64.node && \ cp server-native.node server-native.armv7.node; \ fi && \ ls -la server-native*.node # ============================================================================= # Stage 3: server bundle (dist/main.js) # ============================================================================= FROM base AS server-dist COPY --from=native /app/packages/backend/native/*.node /app/packages/backend/native/ RUN yarn workspace @affine/server build # ============================================================================= # Stage 4: production node_modules (per target arch) # Reuse base (full install already done & cached), then prune to server prod # deps. supportedArchitectures covers both cpus so optional native deps for # either arch get fetched. # ============================================================================= FROM base AS deps ARG TARGETARCH RUN yarn config set --json supportedArchitectures.cpu "[\"x64\", \"arm64\"]" && \ yarn config set --json supportedArchitectures.libc "[\"glibc\"]" # Re-install with both cpu targets so the image works on amd64+arm64 builds. RUN yarn install --immutable && \ yarn workspaces focus @affine/server @types/affine__env --production # Regenerate prisma client engines for this build platform's node_modules RUN cd packages/backend/server && yarn prisma generate # Move to server dir layout expected by docker-clean.mjs & runtime stage RUN mv /app/node_modules /app/packages/backend/server/node_modules # ============================================================================= # Stage 5: runtime — mirror .github/deployment/node/Dockerfile layout # ============================================================================= FROM node:22-bookworm-slim AS runtime ARG TARGETARCH WORKDIR /app COPY --from=deps /app/packages/backend/server/node_modules /app/node_modules COPY --from=server-dist /app/packages/backend/server/dist /app/dist COPY --from=server-dist /app/packages/backend/server/scripts /app/scripts COPY --from=frontend /app/packages/frontend/apps/web/dist /app/static COPY --from=frontend /app/packages/frontend/admin/dist /app/static/admin COPY --from=frontend /app/packages/frontend/apps/mobile/dist /app/static/mobile # Mirror upstream layout: /app IS the server package dir — the selfhost # predeploy job runs `yarn prisma migrate deploy` + `yarn cli` from here. COPY --from=base /app/packages/backend/server/package.json /app/package.json COPY --from=base /app/packages/backend/server/schema.prisma /app/schema.prisma COPY --from=base /app/packages/backend/server/migrations /app/migrations # Upstream installs these in the final image (jemalloc for ENV below, # openssl for prisma engines). RUN apt-get update && \ apt-get install -y --no-install-recommends openssl libjemalloc2 && \ rm -rf /var/lib/apt/lists/* # Native binding for this target arch. node-loader emitted # `require('./server-native.node')` inside dist/main.js — the binary must sit # next to it. COPY --from=native /app/packages/backend/native/server-native.node /app/dist/server-native.node ENV LD_PRELOAD=libjemalloc.so.2 # Same pruning as upstream: sourcemaps, wrong-arch natives/prisma engines, # dedupe static files. RUN AFFINE_DOCKER_CLEAN=1 \ TARGETARCH="$( [ "$TARGETARCH" = "amd64" ] && echo amd64 || echo arm64 )" \ node ./scripts/docker-clean.mjs EXPOSE 3010 CMD ["node", "./dist/main.js"]