Two OpenSSH-on-alpine quirks caught by a real ssh attempt:
1) alpine's `adduser -D` leaves shadow password as `!`, which
OpenSSH 9.x treats as 'account locked' and refuses even for
pubkey auth (logs: 'User agent not allowed because account is
locked'). Sed-replace `!` with `*` post-create — no password
set, but account NOT locked.
2) Setting the login shell to /sbin/nologin defeats ForceCommand,
because sshd executes the forced command as
`<login-shell> -c "<command>"`. nologin then prints
'This account is not available' and exits. Use /bin/sh instead;
the security boundary is ForceCommand + sshd_config, not the
shell — clients cannot bypass ForceCommand to ask for an
interactive shell.
README security section updated to reflect both points.
- HTTP path: opt-in via $HTTP_TOKEN; busybox httpd binds $HTTP_PORT
(default 8080) and serves /cgi-bin/run, which validates the
'Authorization: Bearer …' header and exec's the same force-command
wrapper SSH uses. Output streams chunked. GET and POST both work.
Without HTTP_TOKEN the bastion stays SSH-only.
- README rewritten with shields.io badges, two complete quickstart
examples (WordPress drop-in + nginx config-reload webhook), inline
comments on every yaml line marking required/optional, traefik
integration in both examples, and star-history footer matching
Blax OSS convention.
- Dockerfile: add busybox-extras (the httpd applet was split out of
the core busybox binary in alpine 3.21); EXPOSE 8080; document
HTTP_TOKEN/HTTP_PORT env vars.
Named volumes get wiped by 'docker compose down -v' — that command
is in too many people's muscle memory for ssh host keys to live
behind it. Bind-mount /etc/ssh/keys to ./docker-data/bastion-*/keys
instead, matching the laravel-workkit §6 convention.
Minimal SSH bastion (alpine + openssh-server + docker-cli) that
authenticates by key and runs exactly one preconfigured command
(FORCE_COMMAND) per session. authorized_keys can be merged from
both a host-mounted source and a repo-mounted source. Host keys
persist via /etc/ssh/keys volume; docker socket group membership
is aligned at boot.