No description
Find a file
Renovate Bot e8a85434ca
Some checks failed
Build k8s-mgmt-pod image / Build & Push (pull_request) Failing after 16s
Update docker/build-push-action action to v7
2026-06-24 12:01:55 +00:00
.github/workflows Update docker/build-push-action action to v7 2026-06-24 12:01:55 +00:00
docker Update SSH port from 22 to 2222 in Dockerfile, entrypoint, and Kubernetes configurations 2026-06-23 15:48:15 +02:00
k8s Add PersistentVolumeClaim for pod state persistence and update README 2026-06-24 11:01:13 +02:00
.env.example initial commit 2026-06-23 15:06:17 +02:00
.gitignore initial commit 2026-06-23 15:06:17 +02:00
docker-compose.yaml Update SSH port from 22 to 2222 in Dockerfile, entrypoint, and Kubernetes configurations 2026-06-23 15:48:15 +02:00
Dockerfile Merge pull request 'Update dependency derailed/k9s to v0.51.0' (#2) from renovate/derailed-k9s-0.x into main 2026-06-24 11:46:55 +00:00
entrypoint.sh Enhance entrypoint and deployment configurations for Kubernetes management pod 2026-06-23 16:37:14 +02:00
README.md Update SSH port from 22 to 2222 in Dockerfile, entrypoint, and Kubernetes configurations 2026-06-23 15:48:15 +02:00
renovate.json Refactor Dockerfile ARG declarations for better organization and add renovate configuration for automated dependency updates 2026-06-24 12:42:34 +02:00

k8s-mgmt-pod

k8s-mgmt-pod is a Debian-based Kubernetes bastion image with:

  • lfk preinstalled from a pinned GitHub release .deb
  • kubectl preinstalled from a pinned upstream binary release
  • helm preinstalled from a pinned upstream binary release
  • k9s preinstalled from a pinned GitHub release tarball
  • kubectl and helm bash autocompletion enabled for both admin and user
  • local openssh-server on port 2222
  • a browser-based SSH client on port 3000
  • a custom ASCII-art MOTD on login
  • two provisioned users: admin and user

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 local sshd
  • 3000: browser UI entrypoint; / and /ssh are forced to a fixed local WebSSH2 session for 127.0.0.1 and 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:

  1. If *_SSH_PUBKEY is non-empty, the entrypoint writes authorized_keys, fixes ownership and permissions, and enables SSH key login for that user.
  2. If *_PASSWORD is set, the entrypoint also sets that password, even when a public key is present.
  3. 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.
  4. 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.

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/amd64 only
  • pushes only for non-PR runs
  • tags main as latest, date-based, and date-plus-short-sha
  • tags non-main branches with dev-<branch>-*

Kubernetes deployment

Plain manifests live under k8s/README.md and the rest of the k8s directory.