Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,57 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
lint:
name: Lint
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.3.0
with:
dockerfile: Dockerfile
config: .hadolint.yaml

- name: Lint shell scripts
run: |
sudo apt-get update && sudo apt-get install -y shellcheck
shellcheck test/smoke.sh include/etc/profile.d/*.sh include/etc/bash/*.sh \
include/usr/local/bin/_add_binenv include/usr/local/bin/_add_dbin \
include/usr/local/bin/podshell-motd

smoke-test:
name: Build and smoke test
needs: lint
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.1.0

- name: Build image for the local platform
uses: docker/build-push-action@v7.2.0
with:
context: .
platforms: linux/amd64
load: true
tags: podshell:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run smoke tests
run: ./test/smoke.sh podshell:ci

build-and-push:
name: Build and push
needs: smoke-test
runs-on: ubuntu-24.04
permissions:
contents: read
Expand All @@ -41,6 +91,11 @@ jobs:
with:
cosign-release: "v2.2.3"

- name: Set up QEMU
uses: docker/setup-qemu-action@v4
with:
platforms: arm64,amd64

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4.1.0
with:
Expand Down Expand Up @@ -79,6 +134,8 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max
sbom: true

- name: Verify multi-platform manifest
if: github.event_name != 'pull_request'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/registry-cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Registry cleanup
on:
workflow_dispatch:

# NOTE: the build publishes SBOM and provenance attestations, which show up as
# untagged manifests in GHCR. They are referenced by the image index, so only
# run this cleanup deliberately and keep enough versions around.
jobs:
house-keeping:
runs-on: ubuntu-latest
Expand Down
5 changes: 5 additions & 0 deletions .hadolint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
ignored:
# Pinning apk package versions is not practical here: the image tracks the
# current Alpine release and is rebuilt regularly.
- DL3018
59 changes: 59 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# AGENTS.md

Guidance for AI coding agents (and new contributors) working in this repository.

## What this project is

`podshell` is a small Alpine-based container image with debug and development tooling, meant to be
shelled into: as a throwaway pod, an ephemeral `kubectl debug` container, a sidecar, a privileged
daemonset, or plain `docker run`. It runs as the unprivileged user `podshell` (uid/gid 1000) with
passwordless `sudo`, so it works under restrictive admission policies.

Two runtime package managers keep the image small: [`binenv`](https://github.com/devops-works/binenv)
and [`dbin`](https://github.com/xplshn/dbin). Prefer them over adding large or niche packages to the
image.

## Layout

| Path | Purpose |
|---|---|
| `Dockerfile` | Single-stage Alpine build: apk packages, user creation, binenv/dbin bootstrap |
| `include/` | Overlay copied to `/` in the image (profile scripts, motd, sudoers, helper scripts) |
| `include/usr/local/bin/_add_binenv`, `_add_dbin` | Bootstrap scripts run at build time as the `podshell` user |
| `include/etc/profile.d/*.sh` | Sourced by login shells |
| `include/etc/bash/motd.sh` | Sourced by `/etc/bash/bashrc` for interactive shells (`kubectl exec -it -- bash`) |
| `k8s/` | Ready-to-use manifests: pod, sidecar, privileged daemonset and deployment |
| `test/smoke.sh` | Post-build verification of identity, PATH and bundled tooling |
| `.github/workflows/ci.yml` | Smoke test job, then multi-arch build, push and cosign signing |

## Build and test commands

```bash
make build # local platform build, tags ghcr.io/mpepping/podshell:latest
make build-all # multi-arch buildx build (amd64 + arm64), no push - mirrors CI
make lint # hadolint on the Dockerfile
make smoke # run test/smoke.sh against the built image
make start # interactive shell in the built image
```

There are no unit tests; `test/smoke.sh` is the test suite. Run it after any change to the
`Dockerfile` or `include/`.

## Conventions

- Keep the apk package list alphabetically sorted.
- Every shipped tool needs a check in `test/smoke.sh` and a row in the README "Included tooling"
table.
- Shell scripts under `include/` must be POSIX `sh` compatible unless they live in `/etc/bash/`,
and should carry a `# shellcheck shell=...` directive.
- Non-interactive invocations (`kubectl exec pod -- some-command`) must keep stdout clean: the motd
is only printed for interactive shells, guarded by `PODSHELL_MOTD_SHOWN`.
- `PATH` is set both via `ENV` in the Dockerfile (for non-login shells) and via
`include/etc/profile.d/bin-paths.sh` (for `su`/`sudo -i`). Keep both in sync.
- Both `linux/amd64` and `linux/arm64` must build; architecture detection in the bootstrap scripts
maps `x86_64` to `amd64` and `aarch64` to `arm64`.

## Release flow

Pushing to `main` publishes `:main` and `:sha-*` tags. Pushing a `*.*.*` tag publishes semver tags
plus `latest`. All pushed images are signed keylessly with cosign via GitHub OIDC.
31 changes: 29 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,39 @@ LABEL org.opencontainers.image.source="https://github.com/mpepping/podshell"
LABEL org.opencontainers.image.ref.name="ghcr.io/mpepping/podshell"

RUN apk add --no-cache \
apache2-utils \
atop \
bash \
bash-completion \
bat \
bind-tools \
bridge-utils \
conntrack-tools \
curl \
drill \
ethtool \
file \
fping \
git \
htop \
iftop \
iperf3 \
iproute2 \
ipset \
iptables \
iputils \
ipvsadm \
jq \
less \
lsblk \
lsof \
man-db \
man-pages \
mtr \
ncurses \
netcat-openbsd \
nftables \
ngrep \
nmap \
openssh-client \
openssl \
Expand All @@ -39,18 +56,28 @@ RUN apk add --no-cache \
strace \
sudo \
tcpdump \
tcptraceroute \
tmux \
traceroute \
tree \
util-linux-misc \
vim \
virt-what \
websocat \
wget

ADD include/ /
COPY include/ /

RUN usermod -s /bin/bash root && \
addgroup -g 1000 podshell && \
adduser -D -u 1000 -G podshell -s /bin/bash -g "Podshell User" podshell && \
su - podshell -c "/usr/local/bin/_add_binenv" && \
su - podshell -c "/usr/local/bin/_add_dbin --install /home/podshell/.local/bin/dbin"
su - podshell -c "/usr/local/bin/_add_dbin --install /home/podshell/.local/bin/dbin" && \
chmod -R g=u /home/podshell /etc/motd

# Keep binenv/dbin on PATH for every entrypoint, including non-login shells
# such as `kubectl exec -it <pod> -- binenv install <pkg>`.
ENV PATH="/home/podshell/.local/bin:/home/podshell/.binenv:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

USER 1000
WORKDIR /home/podshell
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Martijn Pepping

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
27 changes: 24 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# vim:ft=make:
APP_NAME=ghcr.io/mpepping/podshell
OS_NAME := $(shell uname -s | tr A-Z a-z)
PLATFORMS ?= linux/amd64,linux/arm64

# Auto-detect container runtime
CONTAINER_RUNTIME := $(shell which container 2>/dev/null || which docker 2>/dev/null || which podman 2>/dev/null || echo "")
Expand All @@ -11,15 +12,32 @@ endif


help: ## This help.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.DEFAULT_GOAL := help

.PHONY: help build push pull clean start stop test runtime
.PHONY: help build build-amd64 build-arm64 build-all lint push pull clean start stop test smoke runtime

build: ## Build the image
build: ## Build the image for the local platform
$(CONTAINER_RUNTIME) build -t $(APP_NAME):latest .

build-amd64: ## Build the image for linux/amd64
$(CONTAINER_RUNTIME) build --platform linux/amd64 -t $(APP_NAME):latest .

build-arm64: ## Build the image for linux/arm64
$(CONTAINER_RUNTIME) build --platform linux/arm64 -t $(APP_NAME):latest .

build-all: ## Build multi-platform (see PLATFORMS), without pushing (mirrors CI)
docker buildx build --platform $(PLATFORMS) --output "type=image,push=false" --file ./Dockerfile .

lint: ## Lint the Dockerfile (hadolint) and shell scripts (shellcheck)
$(CONTAINER_RUNTIME) run --rm -i -v $(PWD)/.hadolint.yaml:/.config/hadolint.yaml \
ghcr.io/hadolint/hadolint hadolint --config /.config/hadolint.yaml - < Dockerfile
$(CONTAINER_RUNTIME) run --rm -v $(PWD):/mnt -w /mnt koalaman/shellcheck:stable \
test/smoke.sh include/etc/profile.d/*.sh include/etc/bash/*.sh \
include/usr/local/bin/_add_binenv include/usr/local/bin/_add_dbin \
include/usr/local/bin/podshell-motd

push: ## Push the image
ifneq ($(findstring container,$(CONTAINER_RUNTIME)),)
$(CONTAINER_RUNTIME) image push $(APP_NAME):latest
Expand Down Expand Up @@ -51,6 +69,9 @@ test: ## Test the container build
$(CONTAINER_RUNTIME) run -it --rm $(APP_NAME):latest \
"env | sort && binenv version && dbin info"

smoke: ## Run the smoke test suite against the built image
CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) ./test/smoke.sh $(APP_NAME):latest

runtime: ## Show detected container runtime and OS
@echo "Using container runtime: $(CONTAINER_RUNTIME) on $(OS_NAME)"

Loading
Loading