Fix ssh from host not working in docker
This commit is contained in:
141
Dockerfile
141
Dockerfile
@@ -1,5 +1,29 @@
|
|||||||
# Multi-stage Dockerfile.
|
# Multi-stage Dockerfile.
|
||||||
#
|
#
|
||||||
|
# How to build
|
||||||
|
# ────────────
|
||||||
|
# This Dockerfile needs SSH access to git.kadil.dev during the
|
||||||
|
# `npm install` step (the `goods-editor` dependency is a git+ssh:
|
||||||
|
# URL). The build forwards your host's SSH agent into the build
|
||||||
|
# context via BuildKit's --ssh flag, so no keys are ever baked into
|
||||||
|
# the image.
|
||||||
|
#
|
||||||
|
# Prerequisites on the build host:
|
||||||
|
# • Docker 23+ (BuildKit is the default; older Docker needs
|
||||||
|
# `DOCKER_BUILDKIT=1` exported in the environment).
|
||||||
|
# • An ssh-agent running with the key that has read access to
|
||||||
|
# git.kadil.dev loaded into it:
|
||||||
|
# ssh-add ~/.ssh/id_ed25519 # or whichever key
|
||||||
|
# ssh-add -l # verify
|
||||||
|
#
|
||||||
|
# Build command:
|
||||||
|
# docker build --ssh default -t apparel-designer .
|
||||||
|
#
|
||||||
|
# With docker-compose, the corresponding wiring is in
|
||||||
|
# docker-compose.yml (build.ssh: ["default"]). Compose forwards
|
||||||
|
# the agent the same way; just run:
|
||||||
|
# docker compose build
|
||||||
|
#
|
||||||
# Why two stages
|
# Why two stages
|
||||||
# ──────────────
|
# ──────────────
|
||||||
# `canvas` is a native-binding npm package. It needs Cairo/Pango/etc.
|
# `canvas` is a native-binding npm package. It needs Cairo/Pango/etc.
|
||||||
@@ -12,8 +36,7 @@
|
|||||||
# (a) keep python3+make+g++ in the final image — inflates the image
|
# (a) keep python3+make+g++ in the final image — inflates the image
|
||||||
# by ~200MB of build tools nothing uses at runtime, or
|
# by ~200MB of build tools nothing uses at runtime, or
|
||||||
# (b) try to install --omit=dev in a runtime-only image — fails
|
# (b) try to install --omit=dev in a runtime-only image — fails
|
||||||
# because canvas tries to compile and has no python (this was
|
# because canvas tries to compile and has no python.
|
||||||
# the bug fixed in this revision).
|
|
||||||
#
|
#
|
||||||
# Two-stage solves both: install + compile + prune dev deps in the
|
# Two-stage solves both: install + compile + prune dev deps in the
|
||||||
# builder (which has the toolchain), then COPY the resulting
|
# builder (which has the toolchain), then COPY the resulting
|
||||||
@@ -28,54 +51,93 @@
|
|||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
# Native-binding build prerequisites:
|
# Build-time packages, grouped by why they're here:
|
||||||
# • python3 / make / g++ — node-gyp toolchain for compiling canvas
|
#
|
||||||
# • cairo-dev / pango-dev / etc. — headers canvas links against
|
# Native-binding compile toolchain (for `canvas` + node-gyp):
|
||||||
# These stay in the builder stage only; the runtime stage carries
|
# python3, make, g++
|
||||||
# the matching runtime libraries (no -dev suffix) without the
|
#
|
||||||
# toolchain.
|
# Native-binding headers (canvas links against these at compile
|
||||||
|
# time; the runtime stage carries the matching shared libs):
|
||||||
|
# cairo-dev, pango-dev, libjpeg-turbo-dev, giflib-dev,
|
||||||
|
# librsvg-dev, pixman-dev
|
||||||
|
#
|
||||||
|
# Git over SSH (for the `goods-editor` git+ssh: dependency):
|
||||||
|
# git, openssh-client
|
||||||
|
#
|
||||||
|
# git is npm's transport for git+ssh URLs; openssh-client provides
|
||||||
|
# the ssh binary that git invokes plus the ssh-keyscan utility we
|
||||||
|
# use below to populate known_hosts.
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
cairo-dev pango-dev libjpeg-turbo-dev giflib-dev librsvg-dev pixman-dev \
|
cairo-dev pango-dev libjpeg-turbo-dev giflib-dev librsvg-dev pixman-dev \
|
||||||
python3 make g++
|
python3 make g++ \
|
||||||
|
git openssh-client
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package manifests first so docker can cache the npm install
|
# Copy package manifests first so docker can cache the npm install
|
||||||
# layer when only application source changes.
|
# layer when only application source changes.
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
|
||||||
|
# Install dependencies with SSH agent forwarded from the host.
|
||||||
|
#
|
||||||
|
# `--mount=type=ssh` is a BuildKit feature that exposes the host's
|
||||||
|
# SSH agent socket inside this single RUN step. The agent is NOT
|
||||||
|
# baked into the image — it's only available for the duration of
|
||||||
|
# this command. After the layer is committed, no SSH material
|
||||||
|
# remains.
|
||||||
|
#
|
||||||
|
# Steps:
|
||||||
|
# 1. Create ~/.ssh with 0700 perms (ssh refuses to read keys from
|
||||||
|
# world-readable directories).
|
||||||
|
# 2. ssh-keyscan git.kadil.dev — adds the server's host key to
|
||||||
|
# known_hosts so ssh doesn't prompt "Are you sure you want to
|
||||||
|
# continue connecting?" during the npm fetch. Without this,
|
||||||
|
# git's ssh invocation hangs forever (no stdin to answer the
|
||||||
|
# prompt from inside a Docker build).
|
||||||
|
# 3. npm install — for the git+ssh dep, npm shells out to git,
|
||||||
|
# which shells out to ssh, which uses the forwarded agent.
|
||||||
|
#
|
||||||
|
# If the build aborts here with "Could not read from remote
|
||||||
|
# repository" or "Permission denied (publickey)", the agent isn't
|
||||||
|
# forwarding correctly. Verify with `ssh-add -l` on the host (the
|
||||||
|
# key should be listed) and that you're invoking with `--ssh default`.
|
||||||
|
RUN --mount=type=ssh \
|
||||||
|
mkdir -p -m 0700 ~/.ssh && \
|
||||||
|
ssh-keyscan git.kadil.dev >> ~/.ssh/known_hosts 2>/dev/null && \
|
||||||
|
npm install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Pull Google Fonts into ./fonts/ so they end up in the runtime image.
|
|
||||||
# If the build host has no network this step will produce missing-variant
|
|
||||||
# warnings; the build still succeeds and the server falls back to system
|
|
||||||
# fonts.
|
|
||||||
RUN npm run fetch-fonts || echo "Font fetch had warnings; continuing with whatever was downloaded."
|
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Drop dev dependencies (vitest, playwright, eslint, etc.) so the
|
# Drop dev dependencies (eslint, etc.) so the node_modules we copy
|
||||||
# node_modules we copy to runtime is production-only. This is the
|
# to runtime is production-only. Without this step, the runtime
|
||||||
# step that was missing before — without it, the runtime stage had
|
# stage would either ship dev deps it doesn't use, or re-run
|
||||||
# to re-run `npm install` from scratch, which would re-trigger
|
# `npm install --omit=dev` in an image without the compile
|
||||||
# canvas's native compile in an image that doesn't have the
|
# toolchain — which would fail when canvas tries to rebuild.
|
||||||
# toolchain. By pruning here, the runtime stage just copies the
|
|
||||||
# already-compiled directory.
|
|
||||||
RUN npm prune --production
|
RUN npm prune --production
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
# Stage 2: runtime
|
# Stage 2: runtime
|
||||||
# Carries only the runtime C libraries canvas links against plus
|
# Carries only the runtime C libraries canvas links against plus
|
||||||
# the fonts the export pipeline needs. node_modules is copied
|
# system fonts. node_modules is copied whole from the builder;
|
||||||
# whole from the builder; no `npm install` happens here.
|
# no `npm install` happens here, so no SSH is needed at runtime.
|
||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
FROM node:20-alpine
|
FROM node:20-alpine
|
||||||
|
|
||||||
# Cairo + Pango are required by node-canvas at runtime.
|
# Runtime packages:
|
||||||
# The font packages cover the proprietary template families (Impact,
|
#
|
||||||
# Times, Courier, Arial) via free equivalents that fontconfig will
|
# Native-binding runtime libs (matching the -dev headers in the
|
||||||
# alias, plus emoji rendering for sticker exports.
|
# builder stage):
|
||||||
|
# cairo, pango, libjpeg-turbo, giflib, librsvg, pixman
|
||||||
|
#
|
||||||
|
# System fonts for the export pipeline. node-canvas's text rendering
|
||||||
|
# uses fontconfig to resolve family names; these provide free
|
||||||
|
# equivalents of the proprietary template families (Impact, Times,
|
||||||
|
# Courier, Arial) plus emoji support for sticker exports. The
|
||||||
|
# editor module's own bundled fonts (under node_modules/goods-editor/
|
||||||
|
# fonts/) are registered explicitly via registerFont() — they don't
|
||||||
|
# go through fontconfig and don't need to live in /usr/share/fonts.
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
cairo pango libjpeg-turbo giflib librsvg pixman \
|
cairo pango libjpeg-turbo giflib librsvg pixman \
|
||||||
ttf-liberation ttf-dejavu font-noto font-noto-emoji \
|
ttf-liberation ttf-dejavu font-noto font-noto-emoji \
|
||||||
@@ -83,23 +145,26 @@ RUN apk add --no-cache \
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Bring the production node_modules over from the builder rather
|
# Bring the production node_modules over from the builder. This
|
||||||
# than installing fresh. This carries the already-compiled canvas
|
# carries the already-compiled canvas .node binary AND the editor
|
||||||
# .node binary without needing python/make/g++ in this stage.
|
# module's own fonts (under node_modules/goods-editor/fonts/), so
|
||||||
|
# no separate font-copy step is needed.
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
COPY --from=builder /app/package*.json ./
|
COPY --from=builder /app/package*.json ./
|
||||||
|
|
||||||
# Application files. server.js is the entry point; dist/ is the
|
# Application files. server.js is the entry point; dist/ is the
|
||||||
# Vite build output served as static; fonts/ is the result of
|
# Vite build output served as static.
|
||||||
# fetch-fonts above.
|
|
||||||
COPY server.js ./
|
COPY server.js ./
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
COPY --from=builder /app/fonts ./fonts
|
|
||||||
RUN mkdir -p /app/uploads /app/exports
|
RUN mkdir -p /app/uploads /app/exports
|
||||||
|
|
||||||
# Refresh fontconfig's cache so the newly-copied custom fonts are
|
# Refresh fontconfig's cache so the apk-installed system fonts are
|
||||||
# discoverable.
|
# discoverable. The module's own fonts under node_modules are
|
||||||
RUN fc-cache -f /app/fonts || true
|
# registered explicitly by the module's server code (via
|
||||||
|
# registerFont() — see the host's server.js comment about font
|
||||||
|
# registration moving to the module), so they don't need to be
|
||||||
|
# in fontconfig's index.
|
||||||
|
RUN fc-cache -f || true
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
services:
|
services:
|
||||||
apparel-designer:
|
apparel-designer:
|
||||||
build: { context: ., dockerfile: Dockerfile }
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
# Forward the host's SSH agent into the build. Needed because
|
||||||
|
# the `goods-editor` npm dependency uses a git+ssh: URL, so
|
||||||
|
# `npm install` has to authenticate against git.kadil.dev
|
||||||
|
# during the build. "default" means "use the SSH_AUTH_SOCK
|
||||||
|
# environment variable on the host" — matches the build
|
||||||
|
# command `docker build --ssh default`.
|
||||||
|
#
|
||||||
|
# The host running `docker compose build` must have:
|
||||||
|
# • An ssh-agent running with the right key loaded
|
||||||
|
# (verify with `ssh-add -l`).
|
||||||
|
# • Docker 23+ (BuildKit is the default; older versions
|
||||||
|
# need `DOCKER_BUILDKIT=1` exported).
|
||||||
|
#
|
||||||
|
# No SSH material is baked into the image — the agent is only
|
||||||
|
# available during the single RUN step in the Dockerfile that
|
||||||
|
# has `--mount=type=ssh`. See the Dockerfile docblock for the
|
||||||
|
# full mechanism.
|
||||||
|
ssh:
|
||||||
|
- default
|
||||||
container_name: apparel-designer
|
container_name: apparel-designer
|
||||||
ports: ["3001:3001"]
|
ports: ["3001:3001"]
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Reference in New Issue
Block a user