Professional development containers for Ada, C++, Go, and Rust — with embedded (ARM Cortex-M/A) support for Ada, C++, and Rust.
| Image | Language | Base | Architectures | Embedded |
|---|---|---|---|---|
dev-container-ada |
Ada (Alire-managed GNAT) | Ubuntu 22.04 | amd64 | Cortex-M/A |
dev-container-ada-system |
Ada (APT gnat-13) |
Ubuntu 24.04 | amd64, arm64 | Cortex-M/A |
dev-container-cpp |
C++ (Clang 20, CMake, vcpkg) | Ubuntu 24.04 | amd64, arm64 | Cortex-M/A |
dev-container-cpp-system |
C++ (GCC 13, Clang 18, apt) | Ubuntu 24.04 | amd64, arm64 | Cortex-M/A |
dev-container-go |
Go 1.26.1, protobuf, Bazelisk | Ubuntu 24.04 | amd64, arm64 | — |
dev-container-rust |
Rust stable (rustup) | Ubuntu 24.04 | amd64, arm64 | Cortex-M/A |
All images are published to GitHub Container Registry:
ghcr.io/abitofhelp/<image-name>:latest
ghcr.io/abitofhelp/<image-name>:v<X.Y.Z>
All six images share a single repository version. A vX.Y.Z tag on this
repo produces the matching ghcr.io/abitofhelp/<image>:vX.Y.Z for every
image in the same release. The :latest tag always points to the most
recent non-pre-release version. See CHANGELOG.md for the
current release and history.
# From your project directory:
make -f ~/containers/dev_containers/ada/Makefile pull-systemcd ~/Ada/github.com/abitofhelp/my_project
make -f ~/containers/dev_containers/ada/Makefile run-systemThe Makefile auto-detects the container CLI (docker on macOS/Windows, nerdctl
on Linux), generates a sequential container name (dev-container-ada-system-1,
-2, etc.), passes host identity for runtime user adaptation, and mounts your
current directory at /workspace.
If your .zshrc has the convenience functions configured:
adast # Ada system image — cd's to source root, launches container
cppt # C++ upstream image
got # Go image
rustt # Rust image| Command | Image | amd64 (x86_64) | arm64 |
|---|---|---|---|
adat |
dev-container-ada (Alire) | yes | no |
adast |
dev-container-ada-system (APT) | yes | yes |
cppt |
dev-container-cpp | yes | yes |
cppst |
dev-container-cpp-system (APT) | yes | yes |
got |
dev-container-go | yes | yes |
rustt |
dev-container-rust | yes | yes |
Each container provides a reproducible development environment that adapts to the host user at runtime. Any developer can pull a pre-built image and run it without rebuilding.
The included .zshrc detects when it is running inside a container and
visibly marks the prompt:
mike@container /workspace (main) [ctr:rootless]
❯
This prevents common mistakes: editing in the wrong terminal, confusing host and container environments, or debugging UID/mount issues.
| Category | Tools |
|---|---|
| Version control | git, patch, openssh-client |
| Editors | vim, neovim, nano |
| Search | ripgrep (rg), fd-find (fdfind), fzf |
| Network | curl, wget, rsync |
| Archives | tar, zip, unzip, xz, gzip, bzip2 |
| Python | python3, pip3, python3-venv |
| Shell | zsh (default), bash, zsh-autosuggestions, zsh-syntax-highlighting |
| Container | gosu, sudo |
| Debugger | gdb, strace |
| Build | make, pkg-config |
| Pagers | less, more, file, jq, lsof |
| Documentation | typst (formal document compiler) |
See each image's Dockerfile for the full list of language-specific tools.
To keep image builds reproducible, every tool installed in a Dockerfile MUST be pinned to a specific version. When downloading binaries or tarballs, also verify a SHA256 checksum.
Pin at the most specific level available:
- Base images — pin by SHA256 digest (e.g.,
ubuntu:24.04@sha256:…). - Downloadable binaries/tarballs — pin
*_VERSIONand verify*_SHA256. - Package managers — pin tool versions (e.g.,
go install …@v1.2.3,cargo install --version X.Y.Z,rustup toolchain install 1.85.1). - Git clones — check out a tagged release, not a moving branch.
- APT packages — use versioned package names where available
(
gnat-13,clang-20); Ubuntu's archive rotates exact versions, so exact-version apt pinning is discouraged.
Avoid @latest, stable, HEAD, main, and unverified downloads. If
you add a tool, add a TOOLNAME_VERSION (and TOOLNAME_SHA256 where the
tool ships a binary release) to the ARG block at the top of the
Dockerfile.
The Ada images are the current reference for this policy: the Ubuntu base image, Alire, GNAT, and GPRBuild are all version-pinned with SHA256 verification where applicable.
The image ships with a fallback user (dev:1000:1000) for CI and Kubernetes.
At run time, entrypoint.sh reads HOST_USER, HOST_UID, and HOST_GID
from environment variables and adapts the in-container user to match.
The Makefile and container_run.py launcher script automatically detect
the host platform:
| Host | CLI | Runtime |
|---|---|---|
| macOS | docker | Docker Desktop |
| Linux | nerdctl | Rootless containerd |
| Windows | docker | Docker Desktop |
Override with CONTAINER_CLI=docker or the --cli flag.
Containers are named sequentially: dev-container-ada-1, -2, -3, etc.
This allows multiple containers from the same image to run simultaneously
with predictable names.
| Runtime | Container UID 0 is... | Bind mount access via... | Security boundary |
|---|---|---|---|
| Docker rootful | Real root (dangerous) | gosu drop to HOST_UID | Container isolation |
| nerdctl rootless | Host user (safe) | Stay UID 0 (= host user) | User namespace |
| Podman rootless | Host user (safe) | --userns=keep-id | User namespace |
| Kubernetes | Blocked by policy | fsGroup in pod spec | Pod security standards |
Before running containers on a headless Ubuntu server (24.04+), complete these one-time setup steps:
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
sudo sh -c 'echo "kernel.apparmor_restrict_unprivileged_userns=0" >> /etc/sysctl.d/99-rootless.conf'containerd-rootless-setuptool.sh installcontainerd-rootless-setuptool.sh install-buildkitsudo loginctl enable-linger $(whoami)This keeps your systemd session alive across SSH connections so that a second terminal can see running containers.
echo $XDG_RUNTIME_DIR
# Should show: /run/user/<uid>If empty, add to your shell profile:
export XDG_RUNTIME_DIR=/run/user/$(id -u)nerdctl ps # Should return without errorsdev_containers/
├── .github/workflows/ ← matrix build + publish
├── .dockerignore
├── .gitignore
├── entrypoint.sh ← shared across all images
├── LICENSE
├── Makefile.common ← shared Makefile targets
├── README.md
├── USER_GUIDE.md
├── CHANGELOG.md
├── ada/
│ ├── Dockerfile ← Alire-managed toolchain (Ubuntu 22.04)
│ ├── Dockerfile.system ← system toolchain (Ubuntu 24.04)
│ ├── Makefile ← thin: sets vars, includes Makefile.common
│ ├── .zshrc ← Ada-specific shell config
│ └── examples/hello_ada/
├── cpp/
│ ├── Dockerfile ← upstream Clang 20, CMake, vcpkg
│ ├── Dockerfile.system ← system GCC 13, Clang 18
│ ├── Makefile
│ ├── .zshrc
│ └── examples/hello_cpp/
├── go/
│ ├── Dockerfile ← Go 1.26.1, protobuf, Bazelisk
│ ├── Makefile
│ ├── .zshrc
│ └── examples/hello_go/
└── rust/
├── Dockerfile ← Rust stable via rustup
├── Makefile
├── .zshrc
└── examples/hello_rust/
Run make -f <lang>/Makefile help for the full list. Common targets:
make -f ada/Makefile build # Build the image
make -f ada/Makefile run # Launch container (auto-detects CLI)
make -f ada/Makefile run-system # Launch system-toolchain variant
make -f ada/Makefile test # Smoke test
make -f ada/Makefile inspect # Show configured variables
make -f ada/Makefile help # Full target listContainer launch logic lives in container_run.py from the
hybrid_scripts_python
repository. The Makefile auto-detects its location by platform. For
standalone use or ad-hoc projects, clone the scripts repo directly:
# macOS
git clone [email protected]:abitofhelp/hybrid_scripts_python.git \
~/Ada/github.com/abitofhelp/hybrid_scripts_python
# Linux
git clone [email protected]:abitofhelp/hybrid_scripts_python.git \
~/ada/github.com/abitofhelp/hybrid_scripts_pythonOverride the path with the HYBRID_SCRIPTS_PYTHON environment variable
if your clone is elsewhere.
BSD-3-Clause — see LICENSE.
This project was developed by Michael Gardner with AI assistance from Claude (Anthropic) and GPT (OpenAI). AI tools were used for design review, architecture decisions, and code generation. All code has been reviewed and approved by the human author. The human maintainer holds responsibility for all code in this repository.