8.1 KiB
k8s-mgmt-pod
k8s-mgmt-pod is a Debian-based Kubernetes bastion image with:
lfkpreinstalled from a pinned GitHub release.debkubectlpreinstalled from a pinned upstream binary releasehelmpreinstalled from a pinned upstream binary releasek9spreinstalled from a pinned GitHub release tarballkubectlandhelmbash autocompletion enabled for bothadminanduser- local
openssh-serveron port2222 - a browser-based SSH client on port
3000 - a custom ASCII-art MOTD on login
- two provisioned users:
adminanduser
Why WebSSH2
This image uses WebSSH2 for browser access instead of a generic web terminal wrapper. WebSSH2 is an actively maintained SSH client that speaks SSH to the local sshd in the same container, which matches the bastion use case better than wrapping a shell command directly.
One caveat is worth stating clearly: browser-based SSH tools do not keep the private-key handshake entirely in the browser. If a user chooses key-based login in the WebSSH2 UI, the key material is passed to the server-side SSH client for the actual SSH authentication flow.
Ports
2222: native SSH into the container's localsshd3000: browser UI entrypoint;/and/sshare forced to a fixed local WebSSH2 session for127.0.0.1and rewritten back to that host if changed in the URL
Runtime environment variables
The entrypoint configures authentication on every container start. A user can have both SSH public keys and a password at the same time.
| Variable | Meaning |
|---|---|
ADMIN_SSH_PUBKEY |
One or more newline-separated SSH public keys for admin |
USER_SSH_PUBKEY |
One or more newline-separated SSH public keys for user |
ADMIN_PASSWORD |
Optional password for admin; can be used with or without ADMIN_SSH_PUBKEY |
USER_PASSWORD |
Optional password for user; can be used with or without USER_SSH_PUBKEY |
ENABLE_BOOTSTRAP_KUBECONFIG |
Set to true to copy a mounted kubeconfig into both users' homes at startup; defaults to false |
BOOTSTRAP_KUBECONFIG |
Path to the mounted kubeconfig source file when bootstrap is enabled; defaults to /bootstrap/kubeconfig/config |
Behavior per user:
- If
*_SSH_PUBKEYis non-empty, the entrypoint writesauthorized_keys, fixes ownership and permissions, and enables SSH key login for that user. - If
*_PASSWORDis set, the entrypoint also sets that password, even when a public key is present. - If no password is supplied but a public key is present, the entrypoint locks password login for that user and leaves key-based login enabled.
- If neither a public key nor a password is supplied, the entrypoint generates a random password, stores it in the container state directory, applies it, and logs it once to container stdout with a clear label.
If both users end up key-only, sshd is configured for public-key-only login. If either user has an explicit or generated password, sshd enables password and keyboard-interactive authentication so both native SSH and WebSSH2 can use the same underlying auth methods.
Build
docker build --load -t k8s-mgmt-pod:local .
If your Docker CLI is backed by Buildx with the docker-container driver, a plain docker build may finish successfully but leave the image only in the build cache. --load imports the result into your local Docker image store so docker run k8s-mgmt-pod:local works afterward.
Pinned build arguments can be overridden when you need to bump releases:
docker build --load \
--build-arg LFK_VERSION=0.14.9 \
--build-arg KUBECTL_VERSION=1.31.0 \
--build-arg HELM_VERSION=3.16.2 \
--build-arg K9S_VERSION=0.40.10 \
--build-arg WEBSSH2_VERSION=5.0.1 \
-t k8s-mgmt-pod:local .
Run with Docker Compose
The included docker-compose.yaml enables kubeconfig bootstrap for local use. It mounts your host ${HOME}/.kube directory read-only and copies ${HOME}/.kube/config into both /home/admin/.kube/config and /home/user/.kube/config at container startup with the correct ownership and 0600 permissions.
That bootstrap is explicitly opt-in and defaults to off in the container itself, so it does not run in Kubernetes unless you deliberately enable it.
When both are available, the bootstrap kubeconfig is applied after the service-account kubeconfig and therefore takes precedence.
Start it with:
docker compose up --build
Or detached:
docker compose up -d --build
docker compose logs -f
If 2222 or 3000 is already in use on your host, override the published ports:
HOST_SSH_PORT=32222 HOST_WEBSSH_PORT=33000 docker compose up -d --build
You can override the default passwords or inject SSH public keys through your shell environment before starting Compose:
export ADMIN_PASSWORD='change-this-admin-password'
export USER_PASSWORD='change-this-user-password'
export ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)"
export USER_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)"
docker compose up -d --build
You can also start from the example environment file:
cp .env.example .env
docker compose up -d --build
If ${HOME}/.kube/config is missing on the host, the container logs that bootstrap was skipped and continues normally.
Run with SSH public keys
docker run --rm \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
-e USER_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample user@example" \
k8s-mgmt-pod:local
Then connect either with native SSH:
ssh admin@localhost -p 2222
Or in a browser at http://localhost:3000/.
Run with password fallback
docker run --rm \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_PASSWORD='change-this-user-password' \
k8s-mgmt-pod:local
If that fails with a message like unable to upgrade to tcp, received 500, the container may still be fine and the error may be coming from the attached run path in your Docker frontend. Try detached mode and inspect the logs instead:
docker run -d --name k8s-mgmt-pod \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_PASSWORD='change-this-user-password' \
k8s-mgmt-pod:local
docker logs -f k8s-mgmt-pod
If Docker reports bind: address already in use, change the host-side ports or let Docker choose them automatically:
docker run --rm \
-p 127.0.0.1::2222 \
-p 127.0.0.1::3000 \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_PASSWORD='change-this-user-password' \
k8s-mgmt-pod:local
Run with both SSH key and password enabled
docker run --rm \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
-e ADMIN_PASSWORD='change-this-admin-password' \
-e USER_SSH_PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexample user@example" \
-e USER_PASSWORD='change-this-user-password' \
k8s-mgmt-pod:local
With that configuration, each user can authenticate with either their SSH key or their password.
Run with generated password fallback
If you omit both the public-key and password variables for a user, the container generates a random password for that user and prints it once on startup.
docker run --rm \
-p 2222:2222 \
-p 3000:3000 \
-e ADMIN_SSH_PUBKEY="$(cat ~/.ssh/id_ed25519.pub)" \
k8s-mgmt-pod:local
Retrieve the generated password from logs immediately:
docker logs <container-name>
CI publishing
The repository includes .github/workflows/build-workflow.yaml, modeled on the requested reference workflow. It:
- runs weekly, on
main, on matching tags, on pull requests, and manually - detects GitHub vs Forgejo and picks the correct registry and token
- builds
linux/amd64only - pushes only for non-PR runs
- tags
mainaslatest, date-based, and date-plus-short-sha - tags non-
mainbranches withdev-<branch>-*
Kubernetes deployment
Plain manifests live under k8s/README.md and the rest of the k8s directory.