Fix ssh key issues

This commit is contained in:
khalid@traclabs.com
2026-05-24 09:07:09 -05:00
parent 6d63775800
commit 9fe0828986
3 changed files with 169 additions and 55 deletions

View File

@@ -4,25 +4,38 @@
# ────────────
# 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.
# URL). We pass an SSH private key in as a BuildKit secret named
# `ssh_key`. The secret is mounted into the build for just that one
# RUN step, used to authenticate the git fetch, then unmounted —
# nothing about the key ends up in any image layer or in
# `docker history`.
#
# 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
# Get an SSH key with read access to the goods-editor-module repo
# on Gitea registered first (the same key you use for git push works,
# OR a dedicated deploy key with read-only access — recommended for
# CI / servers). The public half goes in Gitea Settings → SSH Keys,
# or as a deploy key on the repo itself.
#
# Build command:
# docker build --ssh default -t apparel-designer .
# Then build, picking whichever invocation matches where the key is:
#
# 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
# 1. From a file on disk (typical local dev / deployment server):
# docker build \
# --secret id=ssh_key,src=$HOME/.ssh/id_ed25519 \
# -t apparel-designer .
#
# 2. From an environment variable (typical CI — secret store
# injects the key as an env var, no file ever touches disk):
# docker build \
# --secret id=ssh_key,env=SSH_PRIVATE_KEY \
# -t apparel-designer .
# (BuildKit 1.5+ supports the `env=` source.)
#
# 3. Via docker-compose: see docker-compose.yml — the `secrets:`
# block there wires the same secret. Then:
# SSH_KEY_FILE=$HOME/.ssh/id_ed25519 docker compose build
#
# Requires Docker 23+ (BuildKit is the default; older Docker needs
# `DOCKER_BUILDKIT=1` exported in the environment).
#
# Why two stages
# ──────────────
@@ -78,32 +91,47 @@ WORKDIR /app
# layer when only application source changes.
COPY package*.json ./
# Install dependencies with SSH agent forwarded from the host.
# Install dependencies using an SSH key passed in as a build secret.
#
# `--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.
# `--mount=type=secret,id=ssh_key` is a BuildKit feature that exposes
# a host-provided secret at /run/secrets/ssh_key inside this single
# RUN step. The secret is NEVER copied into any image layer; once
# the RUN command finishes, the mount disappears and nothing about
# the key remains in the built image. `docker history` won't show
# it either.
#
# 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.
# 1. Create ~/.ssh with 0700 perms (ssh refuses to use a config
# directory that's world-readable).
# 2. ssh-keyscan git.kadil.dev → ~/.ssh/known_hosts. Without this,
# ssh would prompt "Are you sure you want to continue connecting?"
# with no stdin to answer it, and the install would hang forever.
# 3. Run npm install with GIT_SSH_COMMAND pointing at the mounted
# secret. npm shells out to git for the git+ssh: dependency, git
# respects GIT_SSH_COMMAND, ssh uses the key at /run/secrets/ssh_key.
# `IdentitiesOnly=yes` prevents ssh from trying any other key
# it might find (none in this container, but defensive).
#
# 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 \
# Why use GIT_SSH_COMMAND rather than copying the key to ~/.ssh/id_ed25519:
# the secret mount is a clean way to use a file without it ever
# touching the filesystem proper. If we copied, we'd have to be
# careful to `rm` it in the same RUN step before layer commit; with
# GIT_SSH_COMMAND, the file is only ever at /run/secrets/, which
# BuildKit clears automatically.
#
# If the build aborts here:
# • "Permission denied (publickey)" → the secret was provided but
# the key doesn't have access to the repo. Verify the public
# half is registered in Gitea (account settings or deploy key).
# • "required secret 'ssh_key' not provided" → forgot the `--secret`
# flag on the build command (see docblock at top of file).
# • Hangs at "Cloning into …" → ssh-keyscan didn't populate
# known_hosts (network issue inside the build). Try again with
# `--no-cache`.
RUN --mount=type=secret,id=ssh_key \
mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan git.kadil.dev >> ~/.ssh/known_hosts 2>/dev/null && \
GIT_SSH_COMMAND="ssh -i /run/secrets/ssh_key -o IdentitiesOnly=yes -o UserKnownHostsFile=/root/.ssh/known_hosts" \
npm install
COPY . .