From 030a33444d9c0e8e6ef15b5f9d557a98ecb2d3cc Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:37:00 +0200 Subject: [PATCH 01/16] chore: add .pre-commit-config.yaml --- .pre-commit-config.yaml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a39488e..fd5f50f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,24 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0 hooks: - - id: check-added-large-files - - id: check-json - - id: check-toml - - id: check-yaml - - id: end-of-file-fixer - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + - id: check-merge-conflict + - id: check-case-conflict + - id: check-illegal-windows-names + - id: check-symlinks + - id: destroyed-symlinks + + - repo: https://github.com/gitleaks/gitleaks + rev: 83d9cd684c87d95d656c1458ef04895a7f1cbd8e # v8.30.1 + hooks: + - id: gitleaks + + - repo: https://github.com/compilerla/conventional-pre-commit + rev: 91ab4bf57e58b32adf1a122681f6ebe164d081c8 # v4.4.0 + hooks: + - id: conventional-pre-commit + stages: [ commit-msg ] + args: [] From a22a219da27f810bc9e4fa52c35a2e11d1cf2c58 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:39:16 +0200 Subject: [PATCH 02/16] chore: update .gitignore --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d1f7f10..17ec7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,9 @@ Temporary Items # PROJECT # -tmp/history/.bash_history +tmp/ +!tmp/history/.gitkeep +.env.local + +# sbp-skills manifest (machine-specific) +.sbp/ From 585c69533770544a10fdbc1ec294c8e211812cff Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:39:41 +0200 Subject: [PATCH 03/16] chore: update .hadolint.yaml --- .hadolint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.hadolint.yaml b/.hadolint.yaml index a318a0e..afb3bad 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -2,6 +2,7 @@ ignored: - DL3008 # Pin versions in apt get install - DL3050 # Superfluous label(s) present - DL3059 # Multiple consecutive `RUN` instructions + - SC2155 # Declare and assign separately to avoid masking return values. failure-threshold: style format: tty strict-labels: true From c86bea09ee8c8cc269e144468f3d8e686839d3ec Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:40:04 +0200 Subject: [PATCH 04/16] chore: update devcontainer settings --- .devcontainer/devcontainer.json | 35 ++++++++++++------------------ .devcontainer/hooks/post_attach.sh | 16 +++++++++----- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6c744c0..5796b7b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,37 +10,30 @@ "customizations": { "vscode": { + "extensions": [ + "eamodio.gitlens", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", + "jetmartin.bats", + "ms-azuretools.vscode-docker" + ], "settings": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.rulers": [80, 120], - "editor.wordBasedSuggestions": "off", - "explorer.excludeGitIgnore": true, - "extensions.ignoreRecommendations": true, + "editor.defaultFormatter": "ms-azuretools.vscode-docker", "git.enableCommitSigning": true, "terminal.integrated.defaultProfile.linux": "bash", - "terminal.integrated.shellIntegration.decorationsEnabled": "never", - "terminal.integrated.profiles.linux": { "bash": { "path": "/usr/bin/bash" } - } - }, - - "extensions": [ - "eamodio.gitlens", - "EditorConfig.EditorConfig", - "esbenp.prettier-vscode", - "jetmartin.bats", - "ms-azuretools.vscode-docker", - "ms-vscode.makefile-tools" - ] + }, + "terminal.integrated.shellIntegration.decorationsEnabled": "never" + } } }, "mounts": [ - "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock", - "type=bind,consistency=cached,source=${localWorkspaceFolder}/tmp/history,target=/home/dev/.history" + "type=bind,consistency=cached,source=${localWorkspaceFolder}/tmp/history,target=/home/dev/.history", + "type=bind,source=${localEnv:HOME}/.claude/,target=/home/dev/.claude/", + "type=bind,source=${localEnv:HOME}/.claude.json,target=/home/dev/.claude.json" ] } diff --git a/.devcontainer/hooks/post_attach.sh b/.devcontainer/hooks/post_attach.sh index ef2e957..25d899d 100755 --- a/.devcontainer/hooks/post_attach.sh +++ b/.devcontainer/hooks/post_attach.sh @@ -1,11 +1,17 @@ #!/bin/bash +# ############################################################################## +# HOOK: POST START +# ############################################################################## + +# ------------------------------------------------------------------------------ +# DIRENV +# ------------------------------------------------------------------------------ + direnv allow /workspaces/* -sudo chown root:docker /var/run/docker.sock -sudo chmod g+w /var/run/docker.sock +# ------------------------------------------------------------------------------ +# GIT +# ------------------------------------------------------------------------------ ls -d /workspaces/* | xargs git config --global --add safe.directory - -starship preset plain-text-symbols -o ~/.config/starship.toml -starship config container.disabled true From 4906887c2ab9d7d8a8face70e0bd5bbf93a17b88 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:40:30 +0200 Subject: [PATCH 05/16] chore: update env information --- .env | 34 ++++++++++++++++++++++++++++++++++ .envrc | 6 ++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..03b5916 --- /dev/null +++ b/.env @@ -0,0 +1,34 @@ +# ############################################################################## +# ENVIRONMENT VARIABLES +# ############################################################################## + +# +# GENERAL +# + +export DOCKER_BUILDKIT=1 + +# +# PROJECT VARIABLES +# + +export PROJECT_BUILD_DATE="$(date -u +'%Y-%m-%d %H:%M:%S%z')" +export PROJECT_COMMIT="$(git rev-parse HEAD)" +export PROJECT_VERSION="1.4.1" +export PROJECT_DOCKER_ORG="schubergphilis" +export PROJECT_DOCKER_PLATFORMS="linux/arm64,linux/amd64" +export PROJECT_DOCKER_REPOSITORY="devcontainer" +export PROJECT_NAME="devcontainer" +export PROJECT_DOCKER_HOST="ghcr.io" + + +# +# DOCKER IMAGE ARGUMENTS +# + +export DEFAULT_LANG="C.UTF-8" +export DEFAULT_USER_PRIMARY_GROUP="developers" +export DEFAULT_USER_SECONDARY_GROUPS="sudo" +export DEFAULT_USER_SHELL="/bin/bash" +export DEFAULT_USER="dev" +export DEFAULT_LANG="C.UTF-8" diff --git a/.envrc b/.envrc index 7aed2af..2632338 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,4 @@ -export PROJECT_BUILD_DATE="0000-00-00 00:00:00+00:00" -export PROJECT_NAME="devcontainer" +#!/bin/bash + +source_env_if_exists .env +source_env_if_exists .env.local From 61e2abeac77259d3e184559227b34af79b770b12 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:40:56 +0200 Subject: [PATCH 06/16] chore: update pipeline --- .github/workflows/pipeline.yml | 156 +++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 56 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index b655584..d0b1121 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -1,5 +1,21 @@ +# ############################################################################## +# WORKFLOW +# ############################################################################## + +# ------------------------------------------------------------------------------ +# METADATA +# ------------------------------------------------------------------------------ + +# +# GENERAL +# + name: Pipeline +# +# TRIGGER +# + on: pull_request: push: @@ -7,119 +23,147 @@ on: - "v*" workflow_dispatch: +# ############################################################################## +# ENVIRONMENT VARIABLES +# ############################################################################## + env: - DOCKER_BUILDKIT: 1 - PROJECT_DOCKER_CACHE_BACKEND: ${{ vars.PROJECT_DOCKER_CACHE_BACKEND }} - PROJECT_DOCKER_HOST: ${{ vars.PROJECT_DOCKER_HOST }} - PROJECT_DOCKER_ORG: ${{ vars.PROJECT_DOCKER_ORG }} - PROJECT_DOCKER_PLATFORMS: ${{ vars.PROJECT_DOCKER_PLATFORMS }} - PROJECT_NAME: ${{ vars.PROJECT_NAME }} + PROJECT_DOCKER_EXTRA_ARGS: ${{ vars.PROJECT_DOCKER_EXTRA_ARGS }} + +# ############################################################################## +# JOBS +# ############################################################################## jobs: lint: name: Lint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04-arm steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare - uses: schubergphilis/prepare-action@v1 + uses: schubergphilis/prepare-action@68aa577339a0a7307c3c9c4d9141a9b632e1fff8 # v1.0.0 with: type: container job: lint + - name: Load Environment Variables + run: | + direnv allow . + direnv export gha > "$GITHUB_ENV" + - name: Lint run: | - make lint + task lint scan: name: Scan - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04-arm needs: lint steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare - uses: schubergphilis/prepare-action@v1 + uses: schubergphilis/prepare-action@68aa577339a0a7307c3c9c4d9141a9b632e1fff8 # v1.0.0 with: type: container job: scan + - name: Load Environment Variables + run: | + direnv allow . + direnv export gha > "$GITHUB_ENV" + - name: Scan run: | - make scan + task scan - pre-ci: - name: Pre-CI - runs-on: ubuntu-22.04 + ci-arm: + name: CI (arm64) + runs-on: ubuntu-24.04-arm needs: scan - outputs: - platforms: ${{ steps.platforms.outputs.value }} steps: - - name: Get Platforms as JSON - id: platforms + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Prepare + uses: schubergphilis/prepare-action@68aa577339a0a7307c3c9c4d9141a9b632e1fff8 # v1.0.0 + with: + type: container + job: ci + + - name: Load Environment Variables run: | - echo "value=[\"$(echo $PROJECT_DOCKER_PLATFORMS | sed 's/,/\",\"/g')\"]" >> $GITHUB_OUTPUT - - ci: - name: CI - runs-on: ubuntu-22.04 - needs: pre-ci - strategy: - matrix: - platforms: ${{ fromJSON(needs.pre-ci.outputs.platforms) }} - permissions: - attestations: write - contents: read - id-token: write - packages: write + direnv allow . + direnv export gha > "$GITHUB_ENV" + + - name: Build + env: + PROJECT_DOCKER_PLATFORMS: linux/arm64 + run: | + task build + + - name: Test + env: + PROJECT_DOCKER_PLATFORMS: linux/arm64 + run: | + task test + + ci-amd: + name: CI (amd64) + runs-on: ubuntu-24.04 + needs: scan steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare - uses: schubergphilis/prepare-action@v1 + uses: schubergphilis/prepare-action@68aa577339a0a7307c3c9c4d9141a9b632e1fff8 # v1.0.0 with: type: container job: ci - - name: Login to GitHub Registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.PROJECT_DOCKER_HOST }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Load Environment Variables + run: | + direnv allow . + direnv export gha > "$GITHUB_ENV" + + - name: Build + env: + PROJECT_DOCKER_PLATFORMS: linux/amd64 + run: | + task build - name: Test env: - PROJECT_DOCKER_PLATFORMS: ${{ matrix.platforms }} + PROJECT_DOCKER_PLATFORMS: linux/amd64 run: | - make test + task test cd: name: CD - runs-on: ubuntu-22.04 - needs: ci + runs-on: ubuntu-24.04-arm + needs: [ci-amd, ci-arm] if: ${{ startsWith(github.ref, 'refs/tags/v') }} - permissions: - attestations: write - contents: read - id-token: write - packages: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare - uses: schubergphilis/prepare-action@v1 + uses: schubergphilis/prepare-action@68aa577339a0a7307c3c9c4d9141a9b632e1fff8 # v1.0.0 with: type: container job: cd + - name: Load Environment Variables + run: | + direnv allow . + direnv export gha > "$GITHUB_ENV" + - name: Login to GitHub Registry - uses: docker/login-action@v3 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ vars.PROJECT_DOCKER_HOST }} username: ${{ github.actor }} @@ -127,4 +171,4 @@ jobs: - name: Release run: | - make release + task release From a09105d06a6be6b58e060b81b957001b4acb84fa Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:41:15 +0200 Subject: [PATCH 07/16] chore: add CLAUDE.md --- CLAUDE.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e986b6a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,47 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +A Docker container image built on Ubuntu 24.04, designed as a remote development environment for VS Code Dev Containers. It bundles multiple language version managers, infrastructure tools, and developer tooling into a single image published to ghcr.io. + +## Build & test commands + +This repo uses [Task](https://taskfile.dev) (`Taskfile.yml`) as the build system. Environment variables are loaded from `.env` (and optionally `.env.local`) via direnv. + +```bash +task build # Build Docker image(s) for configured platforms +task test # Build test image and run BATS test suite +task lint # Run hadolint against src/Dockerfile and src/Dockerfile.test +task scan # Run checkov security scan with custom policies in policies/ +task clean # Remove built images +task release # Multi-platform buildx push to registry (CI use) +``` + +To run a single test, build the test image manually and exec bats against a specific file: +```bash +docker run --rm bats /tests/unittest.bats +``` + +## Architecture + +### Image layers (`src/Dockerfile`) +The Dockerfile installs tooling in discrete RUN blocks grouped by concern: system packages, language version managers (nvm, pyenv, rbenv, goenv, rustup, tenv), infrastructure tools (tenv/terraform-docs/tflint, checkov, cosign), and developer tools (hadolint, task, uv, pre-commit, Claude CLI, specify, sbp-skills). Each downloaded binary is verified with SHA256. The final layers configure the `dev` user (non-root, sudo-enabled, `developers` primary group) and set up persistent bash history. + +### Test image (`src/Dockerfile.test`) +Extends the main image, installs bats, copies `tests/` into the container, and sets bats as the entrypoint. Tests verify that each expected tool is on PATH and that user/group configuration is correct. + +### Multi-architecture +Builds target `linux/arm64` and `linux/amd64` via Docker Buildx. Platform-specific download URLs are resolved inside the Dockerfile using `TARGETARCH`/`BUILDARCH` ARGs. + +### CI/CD (`.github/workflows/pipeline.yml`) +Sequential jobs: `lint` → `scan` → `ci` (matrix per platform, runs `make test`) → `cd` (release, tag-only). Images are pushed to ghcr.io; OIDC attestations are generated on release. + +## Key conventions + +- **Hadolint config** (`.hadolint.yaml`): DL3008 (pin apt versions), DL3059 (consecutive RUN), and SC2155 are suppressed. `strict-labels: true` is enforced. +- **Checkov policies** live in `policies/` and are passed via `--external-checks-dir`. +- **Pre-commit hooks** include gitleaks (secret scanning) and conventional-commit message enforcement. +- **Version pinning**: all tool versions are defined as ARGs near the top of the Dockerfile and verified with checksums — update both together. +- **`dev` user filesystem**: `/home/dev/.history/` for persistent bash history; `/home/dev/.local/bin/` for user-installed binaries; Claude config mounted from host in devcontainer. From 752c38f22ff75fe40ee9fed5e172a966b406c47b Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:41:40 +0200 Subject: [PATCH 08/16] chore: use task instead of make --- Makefile | 180 --------------------------------------------------- Taskfile.yml | 124 +++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 180 deletions(-) delete mode 100644 Makefile create mode 100644 Taskfile.yml diff --git a/Makefile b/Makefile deleted file mode 100644 index 3330092..0000000 --- a/Makefile +++ /dev/null @@ -1,180 +0,0 @@ -# -# Make -# - -SHELL := /bin/bash - -# -# Directories -# - -ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -POLICIES_DIR := $(abspath ${ROOT_DIR}/policies) -SOURCE_DIR := $(abspath ${ROOT_DIR}/src) -TESTS_DIR := $(abspath ${ROOT_DIR}/tests) - -# -# Project: General -# - -PROJECT_BUILD_DATE ?= $(shell date --rfc-3339=seconds) -PROJECT_COMMIT ?= $(shell git rev-parse HEAD) -PROJECT_NAME ?= $(error PROJECT_NAME is not set) -PROJECT_VERSION ?= $(strip \ - $(if \ - $(value $CI), \ - $(if \ - $(shell git rev-list --tags --max-count=1), \ - $(shell git describe --tags `git rev-list --tags --max-count=1`), \ - $(shell git rev-parse --short HEAD) \ - ), \ - $(shell git rev-parse --short HEAD) \ - ) \ -) - -# -# Project: Docker -# - -PROJECT_DOCKER_BUILDER := builder-$(PROJECT_NAME) -PROJECT_DOCKER_CACHE_BACKEND ?= inline -PROJECT_DOCKER_HOST ?= index.docker.io -PROJECT_DOCKER_ORG ?= $(error PROJECT_DOCKER_ORG is not set) -PROJECT_DOCKER_PLATFORMS ?= linux/arm64,linux/amd64 -PROJECT_DOCKER_REPOSITORY ?= $(PROJECT_NAME) - -# -# Image Arguments -# - -DEFAULT_LANG ?= C.UTF-8 -DEFAULT_USER_PRIMARY_GROUP ?= dev -DEFAULT_USER_SECONDARY_GROUPS ?= sudo,docker -DEFAULT_USER_SHELL ?= /bin/bash -DEFAULT_USER ?= dev - -# ###################################################################################################################### -# TARGETS -# ###################################################################################################################### - -.PHONY: all -all: lint scan build test - -.PHONY: build -build: - @for platform in `echo ${PROJECT_DOCKER_PLATFORMS} | tr ',' ' '`; do \ - arch="$$(echo $$platform | cut -d/ -f2)"; \ - echo "Building $(PROJECT_NAME):$(PROJECT_VERSION)-$$arch"; \ - docker build \ - --build-arg PROJECT_BUILD_DATE="$(PROJECT_BUILD_DATE)" \ - --build-arg PROJECT_COMMIT="$(PROJECT_COMMIT)" \ - --build-arg PROJECT_VERSION="$(PROJECT_VERSION)" \ - --build-arg DEFAULT_LANG="$(DEFAULT_LANG)" \ - --build-arg DEFAULT_USER_PRIMARY_GROUP="$(DEFAULT_USER_PRIMARY_GROUP)" \ - --build-arg DEFAULT_USER_SECONDARY_GROUPS="$(DEFAULT_USER_SECONDARY_GROUPS)" \ - --build-arg DEFAULT_USER_SHELL="$(DEFAULT_USER_SHELL)" \ - --build-arg DEFAULT_USER="$(DEFAULT_USER)" \ - --cache-from "type=$(PROJECT_DOCKER_CACHE_BACKEND)" \ - --cache-to "type=$(PROJECT_DOCKER_CACHE_BACKEND),mode=max" \ - --file "$(SOURCE_DIR)/Dockerfile" \ - --platform "$$platform" \ - --tag "$(PROJECT_NAME):$(PROJECT_VERSION)-$$arch" \ - .; \ - done - - -.PHONY: clean -clean: - @for platform in `echo ${PROJECT_DOCKER_PLATFORMS} | tr ',' ' '`; do \ - arch="$$(echo $$platform | cut -d/ -f2)"; \ - echo "Removing $(PROJECT_NAME):$(PROJECT_VERSION)-$$arch"; \ - docker images \ - --quiet \ - "$(PROJECT_NAME):$(PROJECT_VERSION)-$$arch" >/dev/null 2>&1 \ - && docker rmi \ - --force \ - "$(PROJECT_NAME):$(PROJECT_VERSION)-$$arch" >/dev/null 2>&1; \ - echo "Removing $(PROJECT_NAME)-test:$$arch"; \ - docker images \ - --quiet \ - "$(PROJECT_NAME)-test:$$arch" >/dev/null 2>&1 \ - && docker rmi \ - --force \ - "$(PROJECT_NAME)-test:$$arch" >/dev/null 2>&1; \ - done - -.PHONY: lint -lint: - @echo "Linting ${SOURCE_DIR}/Dockerfile" - @hadolint "${SOURCE_DIR}/Dockerfile" - @echo "Linting ${SOURCE_DIR}/Dockerfile.test" - @hadolint "${SOURCE_DIR}/Dockerfile.test" - -.PHONY: release -release: - @docker buildx inspect \ - --bootstrap \ - --builder "$(PROJECT_DOCKER_BUILDER)" >/dev/null 2>&1 \ - || docker buildx create \ - --bootstrap \ - --name "$(PROJECT_DOCKER_BUILDER)" \ - --platform "$(PROJECT_DOCKER_PLATFORMS)" \ - --use - - @docker buildx build \ - --build-arg PROJECT_BUILD_DATE="$(PROJECT_BUILD_DATE)" \ - --build-arg PROJECT_COMMIT="$(PROJECT_COMMIT)" \ - --build-arg PROJECT_VERSION="$(PROJECT_VERSION)" \ - --build-arg DEFAULT_LANG="$(DEFAULT_LANG)" \ - --build-arg DEFAULT_USER_PRIMARY_GROUP="$(DEFAULT_USER_PRIMARY_GROUP)" \ - --build-arg DEFAULT_USER_SECONDARY_GROUPS="$(DEFAULT_USER_SECONDARY_GROUPS)" \ - --build-arg DEFAULT_USER_SHELL="$(DEFAULT_USER_SHELL)" \ - --build-arg DEFAULT_USER="$(DEFAULT_USER)" \ - --builder "$(PROJECT_DOCKER_BUILDER)" \ - --cache-from "type=$(PROJECT_DOCKER_CACHE_BACKEND)" \ - --cache-to "type=$(PROJECT_DOCKER_CACHE_BACKEND),mode=max" \ - --file "$(SOURCE_DIR)/Dockerfile" \ - --platform "$(PROJECT_DOCKER_PLATFORMS)" \ - --tag "$(PROJECT_DOCKER_HOST)/$(PROJECT_DOCKER_ORG)/$(PROJECT_DOCKER_REPOSITORY):$(PROJECT_VERSION)" \ - --tag "$(PROJECT_DOCKER_HOST)/$(PROJECT_DOCKER_ORG)/$(PROJECT_DOCKER_REPOSITORY):latest" \ - --push \ - . - -.PHONY: scan -scan: - @echo "Scanning $(SOURCE_DIR)/Dockerfile" - @checkov \ - --file "$(SOURCE_DIR)/Dockerfile" \ - --external-checks-dir "$(POLICIES_DIR)" \ - --framework dockerfile \ - --output cli - -.PHONY: reset -reset: clean - @echo "Removing builder $(PROJECT_DOCKER_BUILDER)" - @docker buildx inspect \ - --bootstrap \ - --builder "$(PROJECT_DOCKER_BUILDER)" >/dev/null 2>&1 \ - && docker buildx rm \ - --builder "$(PROJECT_DOCKER_BUILDER)" \ - || echo -n "" - -.PHONY: test -test: build - @for platform in `echo ${PROJECT_DOCKER_PLATFORMS} | tr ',' ' '`; do \ - arch="$$(echo $$platform | cut -d/ -f2)"; \ - echo "Testing $(PROJECT_NAME)-$(PROJECT_VERSION)-test:$$arch"; \ - docker build \ - --build-arg PROJECT_NAME="$(PROJECT_NAME)" \ - --build-arg PROJECT_VERSION="$(PROJECT_VERSION)-$$arch" \ - --cache-from "type=$(PROJECT_DOCKER_CACHE_BACKEND)" \ - --cache-to "type=$(PROJECT_DOCKER_CACHE_BACKEND),mode=max" \ - --file "$(SOURCE_DIR)/Dockerfile.test" \ - --platform "$$platform" \ - --tag "$(PROJECT_NAME)-test:$(PROJECT_VERSION)-$$arch" \ - . \ - && docker run \ - --platform "$$platform" \ - --rm \ - "$(PROJECT_NAME)-test:$(PROJECT_VERSION)-$$arch"; \ - done diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..0cf50ff --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,124 @@ +version: "3" + +# ############################################################################## +# VARIABLES +# ############################################################################## + +vars: + POLICIES_DIR: "policies" + SOURCE_DIR: "src" + +# ############################################################################## +# TASKS +# ############################################################################## + +tasks: + build: + requires: + vars: + - PROJECT_BUILD_DATE + - PROJECT_COMMIT + - PROJECT_NAME + - PROJECT_VERSION + - PROJECT_DOCKER_PLATFORMS + - DEFAULT_LANG + - DEFAULT_USER_PRIMARY_GROUP + - DEFAULT_USER_SECONDARY_GROUPS + - DEFAULT_USER_SHELL + - DEFAULT_USER + cmds: + - for: { var: PROJECT_DOCKER_PLATFORMS, split: "," } + cmd: | + arch="$(echo {{.ITEM}} | cut -d/ -f2)" + docker build \ + --build-arg PROJECT_BUILD_DATE="{{.PROJECT_BUILD_DATE}}" \ + --build-arg PROJECT_COMMIT="{{.PROJECT_COMMIT}}" \ + --build-arg PROJECT_VERSION="{{.PROJECT_VERSION}}" \ + --build-arg DEFAULT_LANG="{{.DEFAULT_LANG}}" \ + --build-arg DEFAULT_USER_PRIMARY_GROUP="{{.DEFAULT_USER_PRIMARY_GROUP}}" \ + --build-arg DEFAULT_USER_SECONDARY_GROUPS="{{.DEFAULT_USER_SECONDARY_GROUPS}}" \ + --build-arg DEFAULT_USER_SHELL="{{.DEFAULT_USER_SHELL}}" \ + --build-arg DEFAULT_USER="{{.DEFAULT_USER}}" \ + --file "{{.SOURCE_DIR}}/Dockerfile" \ + --platform "{{.ITEM}}" \ + {{.PROJECT_DOCKER_EXTRA_ARGS}} \ + --tag "{{.PROJECT_NAME}}:{{.PROJECT_VERSION}}-$arch" \ + . + + clean: + requires: + vars: + - PROJECT_NAME + cmds: + - docker images -q "{{.PROJECT_NAME}}" | sort | uniq | xargs docker rmi --force + - docker images -q "{{.PROJECT_NAME}}-test" | sort | uniq | xargs docker rmi --force + + lint: + cmds: + - hadolint "{{.SOURCE_DIR}}/Dockerfile" + - hadolint "{{.SOURCE_DIR}}/Dockerfile.test" + + release: + requires: + vars: + - PROJECT_BUILD_DATE + - PROJECT_COMMIT + - PROJECT_DOCKER_ORG + - PROJECT_DOCKER_REPOSITORY + - PROJECT_NAME + - PROJECT_VERSION + - PROJECT_DOCKER_PLATFORMS + - DEFAULT_LANG + - DEFAULT_USER_PRIMARY_GROUP + - DEFAULT_USER_SECONDARY_GROUPS + - DEFAULT_USER_SHELL + - DEFAULT_USER + cmds: + - | + docker buildx build \ + --build-arg PROJECT_BUILD_DATE="{{.PROJECT_BUILD_DATE}}" \ + --build-arg PROJECT_COMMIT="{{.PROJECT_COMMIT}}" \ + --build-arg PROJECT_VERSION="{{.PROJECT_VERSION}}" \ + --build-arg DEFAULT_LANG="{{.DEFAULT_LANG}}" \ + --build-arg DEFAULT_USER_PRIMARY_GROUP="{{.DEFAULT_USER_PRIMARY_GROUP}}" \ + --build-arg DEFAULT_USER_SECONDARY_GROUPS="{{.DEFAULT_USER_SECONDARY_GROUPS}}" \ + --build-arg DEFAULT_USER_SHELL="{{.DEFAULT_USER_SHELL}}" \ + --build-arg DEFAULT_USER="{{.DEFAULT_USER}}" \ + --file "{{.SOURCE_DIR}}/Dockerfile" \ + --platform "{{.PROJECT_DOCKER_PLATFORMS}}" \ + --tag "{{.PROJECT_DOCKER_ORG}}/{{.PROJECT_DOCKER_REPOSITORY}}:{{.PROJECT_VERSION}}" \ + --push \ + {{.PROJECT_DOCKER_EXTRA_ARGS}} \ + . + + scan: + cmds: + - | + checkov \ + --file "{{.SOURCE_DIR}}/Dockerfile" \ + --external-checks-dir "{{.POLICIES_DIR}}" \ + --framework dockerfile \ + --output cli + + test: + requires: + vars: + - PROJECT_NAME + - PROJECT_VERSION + - PROJECT_DOCKER_PLATFORMS + cmds: + - for: { var: PROJECT_DOCKER_PLATFORMS, split: "," } + cmd: | + arch="$(echo {{.ITEM}} | cut -d/ -f2)" + docker build \ + --build-arg PROJECT_NAME="{{.PROJECT_NAME}}" \ + --build-arg PROJECT_VERSION="{{.PROJECT_VERSION}}-$arch" \ + --file "{{.SOURCE_DIR}}/Dockerfile.test" \ + --platform "{{.ITEM}}" \ + --tag "{{.PROJECT_NAME}}-test:$arch" \ + {{.PROJECT_DOCKER_EXTRA_ARGS}} \ + . \ + && docker run \ + --platform "{{.ITEM}}" \ + --rm \ + "{{.PROJECT_NAME}}-test:$arch" From 1f835f3f40f6747a15d373ced321869d5c8297f0 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:42:44 +0200 Subject: [PATCH 09/16] doc: update README.md --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/README.md b/README.md index 3ffc539..b75240a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,15 @@ This project defines a container image to be used as a development environment inside [Visual Studio Code](https://code.visualstudio.com/) through the [Remote Dev Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension. +## Table of Contents + +- [What is devcontainer?](#what-is-devcontainer) +- [How can I use it?](#how-can-i-use-it) +- [Examples](#examples) +- [Installed Software](#installed-software) +- [Contributing](#contributing) +- [License](#license) + ## What is devcontainer? Devcontainer is a standard to define remote development environments using @@ -42,6 +51,53 @@ You can also use it as a base image in your `Dockerfile`: FROM ghcr.io/schubergphilis/devcontainer:latest ``` +## Installed Software + +The image is built on **Ubuntu 24.04 (Noble)** and bundles the following tools. All binaries are verified with SHA256 checksums at build time. + +### Language Version Managers + +| Tool | Version | Description | +|------|---------|-------------| +| [nvm](https://github.com/nvm-sh/nvm) | 0.40.4 | Node.js version manager | +| [pyenv](https://github.com/pyenv/pyenv) | 2.6.27 | Python version manager | +| [rbenv](https://github.com/rbenv/rbenv) + [ruby-build](https://github.com/rbenv/ruby-build) | latest | Ruby version manager | +| [goenv](https://github.com/go-nv/goenv) | 3.0.1 | Go version manager | +| [rustup](https://rustup.rs) | latest | Rust toolchain installer (no default toolchain) | +| [tenv](https://github.com/tofuutils/tenv) | 4.10.1 | Version manager for Terraform, OpenTofu, Terragrunt, and Atmos | + +### Infrastructure & IaC Tools + +| Tool | Version | Description | +|------|---------|-------------| +| [tenv](https://github.com/tofuutils/tenv) | 4.10.1 | Multi-version manager for Terraform / OpenTofu ecosystems | +| [terraform-docs](https://github.com/terraform-docs/terraform-docs) | 0.22.0 | Generate documentation from Terraform modules | +| [tflint](https://github.com/terraform-linters/tflint) | 0.61.0 | Terraform linter | +| [cosign](https://github.com/sigstore/cosign) | 3.0.6 | Container image signing and verification | + +### Developer Tools + +| Tool | Version | Description | +|------|---------|-------------| +| [task](https://taskfile.dev) | 3.50.0 | Task runner / build tool (Taskfile) | +| [uv](https://github.com/astral-sh/uv) | 0.11.7 | Fast Python package and project manager | +| [hadolint](https://github.com/hadolint/hadolint) | 2.14.0 | Dockerfile linter | +| [checkov](https://github.com/bridgecrewio/checkov) | 3.2.521 | Infrastructure-as-code security scanner | +| [pre-commit](https://pre-commit.com) | 4.6.0 | Git pre-commit hook framework | +| [Claude CLI](https://claude.ai/code) | 2.1.104 | Anthropic's Claude Code CLI | +| [specify-cli](https://github.com/github/spec-kit) | 0.7.3 | Specification toolkit | +| [sbp-skills](https://github.com/schubergphilis/agents.md) | latest | Schuberg Philis skill extensions for Claude Code | + +## Contributing + +Contributions are welcome. Please open a pull request or file an issue at +[schubergphilis/devcontainer](https://github.com/schubergphilis/devcontainer/issues). + +When updating tool versions in `src/Dockerfile`, always update the corresponding +`ARG`/`ENV` version variable **and** the SHA256 checksum together. See the +[Key conventions](#key-conventions) section in [CLAUDE.md](./CLAUDE.md) for +further guidance on how this project is structured. + ## License ```text From 014694953d4f760dba628f9b80a2ce55b617d6c7 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:42:55 +0200 Subject: [PATCH 10/16] fix: tests --- tests/unittest.bats | 108 ++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/tests/unittest.bats b/tests/unittest.bats index c66f064..fead761 100644 --- a/tests/unittest.bats +++ b/tests/unittest.bats @@ -1,35 +1,20 @@ #!/usr/bin/env bats -# -# DOCKER -# +# ############################################################################## +# TESTS +# ############################################################################## -@test "docker client is available" { - run which docker - [ "$status" -eq 0 ] -} - -@test "docker compose plugin is available" { - run docker compose version - [ "$status" -eq 0 ] -} - -@test "docker buildx plugin is available" { - run docker buildx version - [ "$status" -eq 0 ] -} - -# +# ------------------------------------------------------------------------------ # USER -# +# ------------------------------------------------------------------------------ @test "dev user is created" { run bash -c "grep -E '^dev:x:' /etc/passwd" [ "$status" -eq 0 ] } -@test "dev is the primary group of dev user" { - run bash -c "id dev | grep -E 'gid=[0-9]+\(dev\)'" +@test "developers is the primary group of dev user" { + run bash -c "id dev | grep -E 'gid=[0-9]+\(developers\)'" [ "$status" -eq 0 ] } @@ -38,36 +23,31 @@ [ "$status" -eq 0 ] } -@test "docker is a secondary group of dev user" { - run bash -c "id dev | grep -E 'groups=.*[0-9]+\(docker\)'" - [ "$status" -eq 0 ] -} - -# +# ------------------------------------------------------------------------------ # SYSTEM CONFIGURATION -# +# ------------------------------------------------------------------------------ @test "sudo is configured" { - run ls /etc/sudoers.d/dev + run ls /etc/sudoers.d/developers [ "$status" -eq 0 ] } -# +# ------------------------------------------------------------------------------ # PACKAGES -# +# ------------------------------------------------------------------------------ @test "checkov is installed" { run which checkov [ "$status" -eq 0 ] } -@test "curl is installed" { - run which curl +@test "claude is installed" { + run which claude [ "$status" -eq 0 ] } -@test "delta is installed" { - run which delta +@test "curl is installed" { + run which curl [ "$status" -eq 0 ] } @@ -81,6 +61,11 @@ [ "$status" -eq 0 ] } +@test "goenv is installed" { + run which goenv + [ "$status" -eq 0 ] +} + @test "gpg is installed" { run which gpg [ "$status" -eq 0 ] @@ -91,48 +76,58 @@ [ "$status" -eq 0 ] } +@test "lsd is installed" { + run which lsd + [ "$status" -eq 0 ] +} + @test "make is installed" { run which git [ "$status" -eq 0 ] } -@test "pipx is installed" { - run which pipx +@test "nvm is installed" { + run ls ~/.nvm/nvm.sh [ "$status" -eq 0 ] } -@test "poetry is installed" { - run which poetry +@test "pyenv is installed" { + run which pyenv [ "$status" -eq 0 ] } -@test "pre-commit is installed" { - run which pre-commit +@test "sbp-skills is installed" { + run which sbp-skills [ "$status" -eq 0 ] } -@test "pyenv is installed" { - run which pyenv +@test "specify is installed" { + run which specify + [ "$status" -eq 0 ] +} + +@test "sudo is installed" { + run which sudo [ "$status" -eq 0 ] } -@test "rbenv is installed" { - run which rbenv +@test "task is installed" { + run which task [ "$status" -eq 0 ] } -@test "starship is installed" { - run which starship +@test "tenv is installed" { + run which tenv [ "$status" -eq 0 ] } -@test "sudo is installed" { - run which sudo +@test "terraform-docs is installed" { + run which terraform-docs [ "$status" -eq 0 ] } -@test "tfenv is installed" { - run which tfenv +@test "tflint is installed" { + run which tflint [ "$status" -eq 0 ] } @@ -141,7 +136,12 @@ [ "$status" -eq 0 ] } -@test "zip is installed" { - run which unzip +@test "uv is installed" { + run which uv + [ "$status" -eq 0 ] +} + +@test "wget is installed" { + run which wget [ "$status" -eq 0 ] } From 9bbbf0dbc0fe2c1df701f799ef40e99d40f22c51 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:43:37 +0200 Subject: [PATCH 11/16] fix: refactor and secure installations --- src/Dockerfile | 542 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 422 insertions(+), 120 deletions(-) diff --git a/src/Dockerfile b/src/Dockerfile index f556687..81daac5 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,6 +1,24 @@ +# ############################################################################## +# PRELUDE +# ############################################################################## + +# +# CHECKOV +# + +# This is a container intended to be used as a remote development environment, +# we want to allow the user the ability to become root #checkov:skip=CKV2_DOCKER_1:Ensure that sudo isn't used -FROM ubuntu:22.04 +# ------------------------------------------------------------------------------ +# BASE IMAGE +# ------------------------------------------------------------------------------ + +FROM ubuntu:24.04@sha256:84e77dee7d1bc93fb029a45e3c6cb9d8aa4831ccfcc7103d36e876938d28895b + +# ------------------------------------------------------------------------------ +# ARGUMENTS +# ------------------------------------------------------------------------------ # # DOCKER BUILDKIT @@ -26,159 +44,280 @@ ARG PROJECT_VERSION # ARG DEFAULT_LANG="C.UTF-8" -ARG DEFAULT_USER_PRIMARY_GROUP="dev" -ARG DEFAULT_USER_SECONDARY_GROUPS="sudo,docker" +ARG DEFAULT_USER_PRIMARY_GROUP="developers" +ARG DEFAULT_USER_SECONDARY_GROUPS="sudo" ARG DEFAULT_USER_SHELL="/bin/bash" ARG DEFAULT_USER="dev" +# ------------------------------------------------------------------------------ +# ENVIRONMENT VARIABLES +# ------------------------------------------------------------------------------ + # -# LABELS +# OS # +ENV VERSION_UBUNTU="24.04" +ENV VERSION_UBUNTU_NAME="noble" + +# ------------------------------------------------------------------------------ +# LABELS +# ------------------------------------------------------------------------------ + # # REFERENCE # # - https://github.com/opencontainers/image-spec/blob/main/annotations.md # +LABEL org.opencontainers.artifact.created="${PROJECT_BUILD_DATE}" +LABEL org.opencontainers.artifact.description="Container image to be used as a remote development environment in Visual Studio Code" LABEL org.opencontainers.image.authors="Egon Braun " +LABEL org.opencontainers.image.base.name="docker.io/library/ubuntu:${VERSION_UBUNTU}" LABEL org.opencontainers.image.created="${PROJECT_BUILD_DATE}" LABEL org.opencontainers.image.description="Container image to be used as a remote development environment in Visual Studio Code" +LABEL org.opencontainers.image.documentation="https://github.com/schubergphilis/devcontainer/README.md" LABEL org.opencontainers.image.licenses="ApacheV2" LABEL org.opencontainers.image.revision="${PROJECT_COMMIT}" LABEL org.opencontainers.image.source="https://github.com/schubergphilis/devcontainer" LABEL org.opencontainers.image.title="devcontainer" -LABEL org.opencontainers.image.url="ghcr.io/schubergphilis/devcontainer:latest" +LABEL org.opencontainers.image.url="docker://ghcr.io/schubergphilis/devcontainer" LABEL org.opencontainers.image.vendor="Schuberg Philis" LABEL org.opencontainers.image.version="${PROJECT_VERSION}" -# -# OS -# - -ENV VERSION_UBUNTU="22.04" -ENV VERSION_UBUNTU_NAME="jammy" +# ###################################################################################################################### +# SETUP: BASE +# ###################################################################################################################### SHELL [ "/bin/bash", "-o", "pipefail", "-c" ] +WORKDIR / + +RUN case "${TARGETARCH}" in \ + amd64|arm64) ;; \ + *) echo "Unsupported arch: ${TARGETARCH}" && exit 1 ;; \ + esac + +# ---------------------------------------------------------------------------------------------------------------------- +# INSTALL SYSTEM PACKAGES +# ---------------------------------------------------------------------------------------------------------------------- + RUN export DEBIAN_FRONTEND="noninteractive" \ && apt-get update \ + && apt-get -y install --no-install-recommends ca-certificates curl \ + && mkdir -p /etc/apt/keyrings \ + && curl -sOL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + && mv githubcli-archive-keyring.gpg /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && mkdir -p /etc/apt/sources.list.d \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \ + && apt-get update \ && apt-get -y install --no-install-recommends \ - build-essential \ - ca-certificates \ - curl \ - direnv \ - gcc-aarch64-linux-gnu \ - git \ - git-extras \ - gnupg2 \ - gpg-agent \ - jq \ - libbz2-dev \ - libffi-dev \ - liblzma-dev \ - libncursesw5-dev \ - libprocps-dev \ - libreadline-dev \ - libsqlite3-dev \ - libssl-dev \ - libxi-dev \ - libxi6 \ - libxml2-dev \ - libxmlsec1-dev \ - libxtst-dev \ - libxtst6 \ - locales \ - lsb-release \ - make \ - pipx \ - pkg-config \ - python3 \ - python3-full \ - software-properties-common \ - ssh \ - sudo \ - tk-dev \ - unzip \ - vim \ - xz-utils \ - zip \ - zlib1g-dev \ + autoconf \ + build-essential \ + direnv \ + gdb \ + gh \ + git \ + git-extras \ + gnupg2 \ + gpg-agent \ + jq \ + libbz2-dev \ + libdb-dev \ + libffi-dev \ + libgdbm-dev \ + libgdbm6 \ + libgmp-dev \ + liblzma-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libreadline-dev \ + libreadline6-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libyaml-dev \ + locales \ + lsb-release \ + lsd \ + make \ + patch \ + pipx \ + pkg-config \ + python3 \ + python3-full \ + rustc \ + software-properties-common \ + ssh \ + sudo \ + tk-dev \ + tzdata \ + unzip \ + vim \ + wget \ + xz-utils \ + zlib1g-dev \ && apt-get autoremove -y \ && apt-get clean autoclean -y \ && rm -r /var/cache/* /var/lib/apt/lists/* +# ------------------------------------------------------------------------------ +# INSTALL COSIGN +# ------------------------------------------------------------------------------ + +# +# INSTALL +# + +COPY --from=ghcr.io/sigstore/cosign/cosign:v3.0.6@sha256:de9c65609e6bde17e6b48de485ee788407c9502fa08b8f4459f595b21f56cd00 /ko-app/cosign /usr/local/bin/cosign + +# ------------------------------------------------------------------------------ +# INSTAL UV +# ------------------------------------------------------------------------------ + +# +# INSTALL +# + +COPY --from=ghcr.io/astral-sh/uv:0.11.7@sha256:240fb85ab0f263ef12f492d8476aa3a2e4e1e333f7d67fbdd923d00a506a516a /uv /uvx /usr/local/bin/ + +# ------------------------------------------------------------------------------ +# CONFIGURE LOCALISATION +# ------------------------------------------------------------------------------ + RUN locale-gen "${DEFAULT_LANG}" \ && update-locale LANG="${DEFAULT_LANG}" +# ############################################################################## +# SETUP: CUSTOM (AS ROOT) +# ############################################################################## + # -# INSTALL DOCKER +# Empty # -ENV VERSION_DOCKER_CLI="26.1.4-1" -ENV VERSION_DOCKER_BUILDX_PLUGIN="0.14.1-1" -ENV VERSION_DOCKER_COMPOSE_PLUGIN="2.27.1-1" -ENV FILENAME_DOCKER_SUFFIX="ubuntu.${VERSION_UBUNTU}~${VERSION_UBUNTU_NAME}_${TARGETARCH}.deb" -ENV FILENAME_DOCKER_CLI="docker-ce-cli_${VERSION_DOCKER_CLI}~${FILENAME_DOCKER_SUFFIX}" -ENV FILENAME_DOCKER_BUILDX_PLUGIN="docker-buildx-plugin_${VERSION_DOCKER_BUILDX_PLUGIN}~${FILENAME_DOCKER_SUFFIX}" -ENV FILENAME_DOCKER_COMPOSE_PLUGIN="docker-compose-plugin_${VERSION_DOCKER_COMPOSE_PLUGIN}~${FILENAME_DOCKER_SUFFIX}" -ENV URL_DOCKER_BASE="https://download.docker.com/linux/ubuntu/dists/${VERSION_UBUNTU_NAME}/pool/stable/${TARGETARCH}" -ENV URL_DOCKER_CLI="${URL_DOCKER_BASE}/${FILENAME_DOCKER_CLI}" -ENV URL_DOCKER_BUILDX_PLUGIN="${URL_DOCKER_BASE}/${FILENAME_DOCKER_BUILDX_PLUGIN}" -ENV URL_DOCKER_COMPOSE_PLUGIN="${URL_DOCKER_BASE}/${FILENAME_DOCKER_COMPOSE_PLUGIN}" +# ------------------------------------------------------------------------------ +# INSTALL HADOLINT +# ------------------------------------------------------------------------------ + +# +# ENV +# -RUN curl -sSL -o "/tmp/${FILENAME_DOCKER_CLI}" "${URL_DOCKER_CLI}" \ - && curl -sSL -o "/tmp/${FILENAME_DOCKER_BUILDX_PLUGIN}" "${URL_DOCKER_BUILDX_PLUGIN}" \ - && curl -sSL -o "/tmp/${FILENAME_DOCKER_COMPOSE_PLUGIN}" "${URL_DOCKER_COMPOSE_PLUGIN}" \ - && dpkg -i "/tmp/${FILENAME_DOCKER_CLI}" \ - && dpkg -i "/tmp/${FILENAME_DOCKER_BUILDX_PLUGIN}" \ - && dpkg -i "/tmp/${FILENAME_DOCKER_COMPOSE_PLUGIN}" \ - && groupadd docker \ - && rm -f "/tmp/"*"${FILENAME_DOCKER_SUFFIX}" +ENV VERSION_HADOLINT="2.14.0" # -# INSTALL HADOLINT +# INSTALL # -ENV VERSION_HADOLINT="2.12.0" -ENV URL_HADOLINT="https://github.com/hadolint/hadolint/releases/download/v${VERSION_HADOLINT}/hadolint-Linux-${TARGETARCH}" +RUN case "${TARGETARCH}" in \ + amd64) cpuarch="x86_64" ;; \ + *) cpuarch="${TARGETARCH}" ;; \ + esac \ + && curl -sOL "https://github.com/hadolint/hadolint/releases/download/v${VERSION_HADOLINT}/hadolint-linux-${cpuarch}" \ + && curl -sOL "https://github.com/hadolint/hadolint/releases/download/v${VERSION_HADOLINT}/hadolint-linux-${cpuarch}.sha256" \ + && sha256sum -c "hadolint-linux-${cpuarch}.sha256" \ + && mv "hadolint-linux-${cpuarch}" /usr/local/bin/hadolint \ + && chmod +x /usr/local/bin/hadolint \ + && rm -f /tmp/hadolint-* -RUN curl -sSL -o /usr/local/bin/hadolint "${URL_HADOLINT}" \ - && chmod +x /usr/local/bin/hadolint +# ------------------------------------------------------------------------------ +# INSTALL TENV +# ------------------------------------------------------------------------------ # -# INSTALL GIT DELTA +# ENV # -ENV VERSION_GIT_DELTA="0.17.0" -ENV FILENAME_GIT_DELTA="git-delta_${VERSION_GIT_DELTA}_${TARGETARCH}.deb" -ENV URL_GIT_DELTA_BASE="https://github.com/dandavison/delta/releases/download/${VERSION_GIT_DELTA}" -ENV URL_GIT_DELTA="${URL_GIT_DELTA_BASE}/${FILENAME_GIT_DELTA}" +ENV VERSION_TENV="4.10.1" +ENV URL_TENV="https://github.com/tofuutils/tenv/releases/download/v${VERSION_TENV}/tenv_v${VERSION_TENV}_${TARGETARCH}.deb" +ENV URL_TENV_PEM="https://github.com/tofuutils/tenv/releases/download/v${VERSION_TENV}/tenv_v${VERSION_TENV}_${TARGETARCH}.deb.pem" +ENV URL_TENV_SIG="https://github.com/tofuutils/tenv/releases/download/v${VERSION_TENV}/tenv_v${VERSION_TENV}_${TARGETARCH}.deb.sig" +ENV URL_TENV_CHECKSUMS="https://github.com/tofuutils/tenv/releases/download/v${VERSION_TENV}/tenv_v${VERSION_TENV}_checksums.txt" +ENV URL_TENV_CHECKSUMS_SIG="https://github.com/tofuutils/tenv/releases/download/v${VERSION_TENV}/tenv_v${VERSION_TENV}_checksums.txt.sig" +ENV URL_TENV_CHECKSUMS_PEM="https://github.com/tofuutils/tenv/releases/download/v${VERSION_TENV}/tenv_v${VERSION_TENV}_checksums.txt.pem" -RUN curl -sSL --http1.1 -o "/tmp/${FILENAME_GIT_DELTA}" "${URL_GIT_DELTA}" \ - && dpkg -i "/tmp/${FILENAME_GIT_DELTA}" \ - && rm -f "/tmp/${FILENAME_GIT_DELTA}" +# +# INSTALL +# + +RUN curl -sOL "${URL_TENV_CHECKSUMS}" \ + && curl -sOL "${URL_TENV_CHECKSUMS_SIG}" \ + && curl -sOL "${URL_TENV_CHECKSUMS_PEM}" \ + && curl -sOL "${URL_TENV}" \ + && curl -sOL "${URL_TENV_PEM}" \ + && curl -sOL "${URL_TENV_SIG}" \ + && cosign verify-blob \ + --certificate-identity "https://github.com/tofuutils/tenv/.github/workflows/release.yml@refs/tags/v${VERSION_TENV}" \ + --signature "tenv_v${VERSION_TENV}_checksums.txt.sig" \ + --certificate "tenv_v${VERSION_TENV}_checksums.txt.pem" \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ + "tenv_v${VERSION_TENV}_checksums.txt" \ + && cosign verify-blob \ + --certificate-identity "https://github.com/tofuutils/tenv/.github/workflows/release.yml@refs/tags/v${VERSION_TENV}" \ + --signature "tenv_v${VERSION_TENV}_${TARGETARCH}.deb.sig" \ + --certificate "tenv_v${VERSION_TENV}_${TARGETARCH}.deb.pem" \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ + "tenv_v${VERSION_TENV}_${TARGETARCH}.deb" \ + && sha256sum -c "tenv_v${VERSION_TENV}_checksums.txt" --ignore-missing \ + && dpkg -i "tenv_v${VERSION_TENV}_${TARGETARCH}.deb" \ + && rm -f tenv_* + +# ------------------------------------------------------------------------------ +# INSTALL TERRAFORM-DOCS +# ------------------------------------------------------------------------------ # -# INSTALL STARSHIP +# ENV # -RUN curl -fsSL https://starship.rs/install.sh | sh -s -- --yes +ENV VERSION_TERRAFORM_DOCS="0.22.0" +ENV URL_TERRAFORM_DOCS="https://github.com/terraform-docs/terraform-docs/releases/download/v${VERSION_TERRAFORM_DOCS}/terraform-docs-v${VERSION_TERRAFORM_DOCS}-linux-${TARGETARCH}.tar.gz" +ENV URL_TERRAFORM_DOCS_SHA256="https://github.com/terraform-docs/terraform-docs/releases/download/v${VERSION_TERRAFORM_DOCS}/terraform-docs-v${VERSION_TERRAFORM_DOCS}.sha256sum" # -# INSTALL AWSCLI +# INSTALL # -RUN if [ "${TARGETARCH}" == "arm64" ]; then arch="aarch64"; elif [ "${TARGETARCH}" == "amd64" ]; then arch="x86_64"; else arch="${TARGETARCH}"; fi \ - && curl -vfsSL "https://awscli.amazonaws.com/awscli-exe-linux-${arch}.zip" -o "/tmp/awscliv2.zip" \ - && unzip "/tmp/awscliv2.zip" \ - && ./aws/install \ - && rm -rf "./aws" \ - && rm -f "/tmp/awscliv2.zip" +RUN curl -sOL "${URL_TERRAFORM_DOCS_SHA256}" \ + && curl -sOL "${URL_TERRAFORM_DOCS}" \ + && sha256sum -c "terraform-docs-v${VERSION_TERRAFORM_DOCS}.sha256sum" --ignore-missing \ + && tar -xf "terraform-docs-v${VERSION_TERRAFORM_DOCS}-linux-${TARGETARCH}.tar.gz" \ + && chmod +x terraform-docs \ + && mv terraform-docs /usr/local/bin/ \ + && rm -f terraform-docs-* + + +# ------------------------------------------------------------------------------ +# INSTALL TFLINT +# ------------------------------------------------------------------------------ # -# CREATE DEV USER +# ENV +# + +ENV VERSION_TFLINT="0.61.0" +ENV URL_TFLINT="https://github.com/terraform-linters/tflint/releases/download/v${VERSION_TFLINT}/tflint_linux_${TARGETARCH}.zip" +ENV URL_TFLINT_CHECKSUMS="https://github.com/terraform-linters/tflint/releases/download/v${VERSION_TFLINT}/checksums.txt" + # +# INSTALL +# + + +RUN curl -sOL "${URL_TFLINT}" \ + && curl -sOL "${URL_TFLINT_CHECKSUMS}" \ + && sha256sum -c checksums.txt --ignore-missing \ + && unzip "./tflint_linux_${TARGETARCH}.zip" \ + && chmod +x tflint \ + && mv tflint /usr/local/bin \ + && rm -f ./tflint* checksums.txt + +# ------------------------------------------------------------------------------ +# CREATE DEV USER +# ------------------------------------------------------------------------------ RUN groupadd "${DEFAULT_USER_PRIMARY_GROUP}" \ && useradd \ @@ -187,9 +326,9 @@ RUN groupadd "${DEFAULT_USER_PRIMARY_GROUP}" \ -G "${DEFAULT_USER_SECONDARY_GROUPS}" \ -m "${DEFAULT_USER}" -# +# ------------------------------------------------------------------------------ # CONFIGURE SUDO -# +# ------------------------------------------------------------------------------ RUN echo \ # CONTENT \ @@ -197,15 +336,76 @@ RUN echo \ # END \ >"/etc/sudoers.d/${DEFAULT_USER_PRIMARY_GROUP}" +# ------------------------------------------------------------------------------ +# INSTALL TASK +# ------------------------------------------------------------------------------ + +# +# ENV +# + +ENV VERSION_TASK=3.50.0 +ENV URL_TASK="https://github.com/go-task/task/releases/download/v${VERSION_TASK}/task_linux_${TARGETARCH}.tar.gz" +ENV URL_TASK_CHECKSUMS="https://github.com/go-task/task/releases/download/v${VERSION_TASK}/task_checksums.txt" + +# +# INSTALL +# + +RUN curl -sOL "${URL_TASK}" \ + && curl -sOL "${URL_TASK_CHECKSUMS}" \ + && sha256sum -c task_checksums.txt --ignore-missing \ + && tar zxf task_linux_${TARGETARCH}.tar.gz task \ + && mv task /usr/local/bin/task \ + && chmod +x /usr/local/bin/task \ + && rm -f task_* + +# ------------------------------------------------------------------------------ +# INSTALL GOENV +# ------------------------------------------------------------------------------ + +# +# ENV +# + +ENV VERSION_GOENV="3.0.1" +ENV URL_GOENV="https://github.com/go-nv/goenv/releases/download/${VERSION_GOENV}/goenv_${VERSION_GOENV}_linux_${TARGETARCH}.tar.gz" +ENV URL_GOENV_CHECKSUMS="https://github.com/go-nv/goenv/releases/download/${VERSION_GOENV}/goenv_${VERSION_GOENV}_checksums.txt" + +# +# INSTALL +# + +RUN curl -sOL ${URL_GOENV_CHECKSUMS} \ + && curl -sOL ${URL_GOENV} \ + && sha256sum -c "goenv_${VERSION_GOENV}_checksums.txt" --ignore-missing \ + && tar -xvf "goenv_${VERSION_GOENV}_linux_${TARGETARCH}.tar.gz" goenv \ + && chmod +x goenv \ + && mv goenv /usr/local/bin/ \ + && rm -f goenv_* \ + && echo "eval \"\$(goenv init -)\"" >> "${HOME}/.bashrc" \ + && echo "export PATH=\"\$GOPATH/bin:\$GOROOT/bin:\$PATH\"" >> "${HOME}/.bashrc" + +# ############################################################################## +# SETUP: CUSTOM (AS DEV USER) +# ############################################################################## + USER "${DEFAULT_USER}" +WORKDIR "/home/${DEFAULT_USER}" + +# +# ENV +# + ENV HOME="/home/${DEFAULT_USER}" ENV LANG="${DEFAULT_LANG}" ENV LANGUAGE="${DEFAULT_LANG}" ENV LC_ALL="${DEFAULT_LANG}" -ENV PATH="${HOME}/.local/bin:${PATH}" ENV PROMPT_COMMAND="history -a" ENV HISTFILE="${HOME}/.history/.bash_history" +ENV GOENV_ROOT="${HOME}/.goenv" +ENV PATH="${HOME}/.local/bin:${GOENV_ROOT}/bin:${PATH}" # # GENERAL @@ -216,64 +416,166 @@ RUN mkdir -p "${HOME}/.config" \ && touch "${HOME}/.history/.bash_history" \ && mkdir -p "${HOME}/.local/bin" +# ------------------------------------------------------------------------------ +# INSTALL CLAUDE, SPEC-KIT AND SBP-SKILLS +# ------------------------------------------------------------------------------ + +# +# ENV +# + +ENV VERSION_CLAUDE="2.1.104" + # -# INSTALL TFENV +# INSTALL # -ENV TFENV_VERSION="3.0.0" -ENV TFENV_ROOT="${HOME}/.tfenv" -ENV PATH="${TFENV_ROOT}/bin:${PATH}" +RUN curl -fsSL -o /tmp/install.sh https://claude.ai/install.sh \ + && echo "b315b46925a9bfb9422f2503dd5aa649f680832f4c076b22d87c39d578c3d830 /tmp/install.sh" | sha256sum -c - \ + && chmod +x /tmp/install.sh \ + && /tmp/install.sh "${VERSION_CLAUDE}" \ + && rm -f /tmp/install.sh \ + && uv tool install specify-cli --from "git+https://github.com/github/spec-kit.git@da4f095a49b4adbb39e57966a5da6c686db0152d" # v0.7.3 + +RUN git clone "https://github.com/schubergphilis/agents.md.git" "${HOME}/.cache/sbp-skills/repo" \ + && cp "${HOME}/.cache/sbp-skills/repo/cli/sbp-skills" "${HOME}/.local/bin/sbp-skills" \ + && chmod +x "${HOME}/.local/bin/sbp-skills" -RUN git clone --depth=1 --branch "v${TFENV_VERSION}" "https://github.com/tfutils/tfenv.git" "${TFENV_ROOT}" +# ------------------------------------------------------------------------------ +# INSTALL NVM +# ------------------------------------------------------------------------------ # +# ENV +# + +ENV VERSION_NVM="0.40.4" +ENV URL_NVM="https://raw.githubusercontent.com/nvm-sh/nvm/v${VERSION_NVM}/install.sh" + +# +# INSTALL +# + +RUN curl -fsSL -o /tmp/install.sh "${URL_NVM}" \ + && echo "4b7412c49960c7d31e8df72da90c1fb5b8cccb419ac99537b737028d497aba4f /tmp/install.sh" | sha256sum -c - \ + && chmod +x /tmp/install.sh \ + && /tmp/install.sh \ + && rm -f /tmp/install.sh + +# ------------------------------------------------------------------------------ # INSTALL PYENV +# ------------------------------------------------------------------------------ + +# +# ENV # +ENV URL_PYENV="https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer" +ENV VERSION_PYENV="2.6.27" ENV PYENV_ROOT="${HOME}/.pyenv" ENV PATH="${PYENV_ROOT}/bin:$PATH" -RUN curl -fsSL https://pyenv.run | bash - # -# INSTALL RBENV +# INSTALL # -ENV PATH="${HOME}/.rbenv/bin:${PATH}" +RUN curl -fsSL -o /tmp/pyenv-installer "${URL_PYENV}" \ + && echo "4b0adf623a6205727163eb98610b6c5e63f23b99183948b874d867cd9b30ef13 /tmp/pyenv-installer" | sha256sum -c - \ + && chmod +x /tmp/pyenv-installer \ + && PYENV_GIT_TAG="v${VERSION_PYENV}" /tmp/pyenv-installer \ + && rm -f /tmp/pyenv-installer \ + && echo "eval \"\$(pyenv init --path)\"" >> "${HOME}/.bashrc" \ + && echo "eval \"\$(pyenv virtualenv-init -)\"" >> "${HOME}/.bashrc" -RUN curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash +# ------------------------------------------------------------------------------ +# INSTALL RBENV +# ------------------------------------------------------------------------------ # -# OTHER TOOLS +# ENV # -ENV VERSION_CHECKOV="3.2.136" -ENV VERSION_POETRY="1.8.3" -ENV VERSION_PRECOMMIT="3.7.1" +ENV PATH="${HOME}/.rbenv/bin:$PATH" # -# PYTHON +# INSTALL # -RUN pipx install "checkov==${VERSION_CHECKOV}" \ - && pipx install "poetry==${VERSION_POETRY}" \ - && pipx install "pre-commit==${VERSION_PRECOMMIT}" +RUN git clone https://github.com/rbenv/rbenv.git "${HOME}/.rbenv" \ + && git clone https://github.com/rbenv/ruby-build.git "${HOME}/.rbenv/plugins/ruby-build" \ + && echo "eval \"\$(rbenv init - --no-rehash bash)\"" >> "${HOME}/.bashrc" + + +# ------------------------------------------------------------------------------ +# INSTALL RUSTUP +# ------------------------------------------------------------------------------ + + +RUN curl --proto '=https' --tlsv1.2 -fsSL -o /tmp/rustup-init.sh https://sh.rustup.rs \ + && echo "6c30b75a75b28a96fd913a037c8581b580080b6ee9b8169a3c0feb1af7fe8caf /tmp/rustup-init.sh" | sha256sum -c - \ + && chmod +x /tmp/rustup-init.sh \ + && /tmp/rustup-init.sh -y --default-toolchain none \ + && rm -f /tmp/rustup-init.sh + +# ------------------------------------------------------------------------------ +# INSTALL TOOLS +# ------------------------------------------------------------------------------ # -# CONFIGURE SHELL +# ENV # -RUN echo "eval \"\$(direnv hook bash)\"" >> "${HOME}/.bashrc" \ - && echo "eval \"\$(starship init bash)\"" >> "${HOME}/.bashrc" \ - && echo "eval \"\$(pyenv init --path)\"" >> "${HOME}/.bashrc" \ - && echo "eval \"\$(pyenv virtualenv-init -)\"" >> "${HOME}/.bashrc" +ENV VERSION_CHECKOV="3.2.521" +ENV VERSION_PRECOMMIT="4.6.0" # +# PYTHON +# + +RUN pipx install \ + "checkov==${VERSION_CHECKOV}" \ + "pre-commit==${VERSION_PRECOMMIT}" + +# ------------------------------------------------------------------------------ +# CONFIGURE DIRENV +# ------------------------------------------------------------------------------ + +RUN echo "eval \"\$(direnv hook bash)\"" >> "${HOME}/.bashrc" + +# ------------------------------------------------------------------------------ +# CONFIGURE ALIASES +# ------------------------------------------------------------------------------ + +RUN echo "alias ls=\"lsd\"" >> "${HOME}/.bashrc" + +# ------------------------------------------------------------------------------ +# CONFIGURE GOENV +# ------------------------------------------------------------------------------ + +RUN echo "eval \"\$(goenv init -)\"" >> "${HOME}/.bashrc" + +# ############################################################################## # RUN +# ############################################################################## + +# +# Empty # +# ------------------------------------------------------------------------------ +# HEALTHCHECK +# ------------------------------------------------------------------------------ + HEALTHCHECK NONE +# ------------------------------------------------------------------------------ +# ENTRYPOINT +# ------------------------------------------------------------------------------ + ENTRYPOINT [ ] +# ------------------------------------------------------------------------------ +# COMMAND +# ------------------------------------------------------------------------------ + CMD [ ] From 802d20484ce9a269e3e5c119ffd851e1a0f80401 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 08:45:43 +0200 Subject: [PATCH 12/16] fix: project version --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 03b5916..fdb21da 100644 --- a/.env +++ b/.env @@ -14,7 +14,7 @@ export DOCKER_BUILDKIT=1 export PROJECT_BUILD_DATE="$(date -u +'%Y-%m-%d %H:%M:%S%z')" export PROJECT_COMMIT="$(git rev-parse HEAD)" -export PROJECT_VERSION="1.4.1" +export PROJECT_VERSION="1.2.0" export PROJECT_DOCKER_ORG="schubergphilis" export PROJECT_DOCKER_PLATFORMS="linux/arm64,linux/amd64" export PROJECT_DOCKER_REPOSITORY="devcontainer" From 18c81a65887f7633fd866a5f187a20577c165e39 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 09:05:37 +0200 Subject: [PATCH 13/16] feat: install pipenv and poetry --- src/Dockerfile | 6 +++++- tests/unittest.bats | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 81daac5..5925736 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -526,6 +526,8 @@ RUN curl --proto '=https' --tlsv1.2 -fsSL -o /tmp/rustup-init.sh https://sh.rust # ENV VERSION_CHECKOV="3.2.521" +ENV VERSION_PIPENV="2026.6.1" +ENV VERSION_POETRY="2.4.0" ENV VERSION_PRECOMMIT="4.6.0" # @@ -534,7 +536,9 @@ ENV VERSION_PRECOMMIT="4.6.0" RUN pipx install \ "checkov==${VERSION_CHECKOV}" \ - "pre-commit==${VERSION_PRECOMMIT}" + "pre-commit==${VERSION_PRECOMMIT}" \ + "pipenv==${VERSION_PIPENV}" \ + "poetry==${VERSION_POETRY}" # ------------------------------------------------------------------------------ # CONFIGURE DIRENV diff --git a/tests/unittest.bats b/tests/unittest.bats index fead761..fad79a2 100644 --- a/tests/unittest.bats +++ b/tests/unittest.bats @@ -91,6 +91,16 @@ [ "$status" -eq 0 ] } +@test "pipenv is installed" { + run which pipenv + [ "$status" -eq 0 ] +} + +@test "poetry is installed" { + run which poetry + [ "$status" -eq 0 ] +} + @test "pyenv is installed" { run which pyenv [ "$status" -eq 0 ] From cf9e94dade6477e4358e8c04e157fb9779aa811c Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 10:26:29 +0200 Subject: [PATCH 14/16] feat: install rtk --- src/Dockerfile | 10 +++++++++- tests/unittest.bats | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Dockerfile b/src/Dockerfile index 5925736..e9ddd48 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -417,7 +417,7 @@ RUN mkdir -p "${HOME}/.config" \ && mkdir -p "${HOME}/.local/bin" # ------------------------------------------------------------------------------ -# INSTALL CLAUDE, SPEC-KIT AND SBP-SKILLS +# INSTALL CLAUDE, SPEC-KIT, SBP-SKILLS AND RTK # ------------------------------------------------------------------------------ # @@ -425,6 +425,7 @@ RUN mkdir -p "${HOME}/.config" \ # ENV VERSION_CLAUDE="2.1.104" +ENV VERSION_RTK="0.38.0" # # INSTALL @@ -441,6 +442,13 @@ RUN git clone "https://github.com/schubergphilis/agents.md.git" "${HOME}/.cache/ && cp "${HOME}/.cache/sbp-skills/repo/cli/sbp-skills" "${HOME}/.local/bin/sbp-skills" \ && chmod +x "${HOME}/.local/bin/sbp-skills" +RUN curl -fsSL -o /tmp/install.sh https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh \ + && echo "0c46611d70d0eee64c43c6398f8b1fe057b7f0b052810bbeec09288898ba9a46 /tmp/install.sh" | sha256sum -c - \ + && chmod +x /tmp/install.sh \ + && RTK_VERSION="v${VERSION_RTK}" /tmp/install.sh \ + && rm -f /tmp/install.sh + + # ------------------------------------------------------------------------------ # INSTALL NVM # ------------------------------------------------------------------------------ diff --git a/tests/unittest.bats b/tests/unittest.bats index fad79a2..d33ffb6 100644 --- a/tests/unittest.bats +++ b/tests/unittest.bats @@ -106,6 +106,11 @@ [ "$status" -eq 0 ] } +@test "rtk is installed" { + run which rtk + [ "$status" -eq 0 ] +} + @test "sbp-skills is installed" { run which sbp-skills [ "$status" -eq 0 ] From a3c51e3b636ad4e3c4026623bda6c3fa7db00b4a Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 10:28:32 +0200 Subject: [PATCH 15/16] doc: update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b75240a..7d94659 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,11 @@ The image is built on **Ubuntu 24.04 (Noble)** and bundles the following tools. | [checkov](https://github.com/bridgecrewio/checkov) | 3.2.521 | Infrastructure-as-code security scanner | | [pre-commit](https://pre-commit.com) | 4.6.0 | Git pre-commit hook framework | | [Claude CLI](https://claude.ai/code) | 2.1.104 | Anthropic's Claude Code CLI | +| [rtk](https://github.com/rtk-ai/rtk) | 0.38.0 | RTK AI CLI | | [specify-cli](https://github.com/github/spec-kit) | 0.7.3 | Specification toolkit | | [sbp-skills](https://github.com/schubergphilis/agents.md) | latest | Schuberg Philis skill extensions for Claude Code | +| [pipenv](https://pipenv.pypa.io) | 2026.6.1 | Python dependency management tool | +| [poetry](https://python-poetry.org) | 2.4.0 | Python packaging and dependency manager | ## Contributing From 928b6f1ef6f8c29bb2e9cb95330ab0be59580886 Mon Sep 17 00:00:00 2001 From: Egon Braun Date: Tue, 5 May 2026 10:33:27 +0200 Subject: [PATCH 16/16] doc: add CHANGELOG.md --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a6d61d6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.2.0] - 2026-05-05 + +### Added + +- Installed [`rtk`](https://github.com/rtk-ai/rtk) v0.38.0 (RTK AI CLI) via installer script with SHA256 verification +- Installed [`pipenv`](https://pipenv.pypa.io) v2026.6.1 via `pipx` +- Installed [`poetry`](https://python-poetry.org) v2.4.0 via `pipx` +- Added `CLAUDE.md` with codebase guidance and conventions for Claude Code +- Added `.pre-commit-config.yaml` with gitleaks (secret scanning) and conventional-commit enforcement hooks +- Added `Taskfile.yml` as the primary build system, replacing `Makefile` + +### Changed + +- Replaced `Makefile` with `Taskfile.yml` for build orchestration (`task build`, `task test`, `task lint`, `task scan`, `task clean`, `task release`) +- Refactored and hardened tool installation blocks in `src/Dockerfile`: split into discrete, consistently structured `RUN` layers with SHA256 verification for all downloaded scripts +- Updated CI/CD pipeline (`.github/workflows/pipeline.yml`) with revised job structure and environment handling +- Updated `.devcontainer/devcontainer.json` and `hooks/post_attach.sh` for improved local dev setup +- Updated `.env` and `.envrc` with full project environment variable definitions +- Updated `.hadolint.yaml` configuration +- Updated `.gitignore` to cover additional local artifacts +- Updated `README.md` with full installed-software table including all tool versions + +### Fixed + +- Corrected `PROJECT_VERSION` value in `.env` +- Aligned BATS test suite (`tests/unittest.bats`) with refactored Dockerfile installation paths and tool names