https://github.com/abitofhelp/dev_containers
'dev_containers' provides professional development containers for desktop and embedded (ARM Cortex-M/A) platforms.
https://github.com/abitofhelp/dev_containers
ada ada-2022 arm-cortex-a arm-cortex-m containerd cpp dev-container docker go kubernetes linux rust ubuntu zshembedded
Last synced: 5 days ago
JSON representation
'dev_containers' provides professional development containers for desktop and embedded (ARM Cortex-M/A) platforms.
- Host: GitHub
- URL: https://github.com/abitofhelp/dev_containers
- Owner: abitofhelp
- License: bsd-3-clause
- Created: 2026-04-07T00:15:31.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-06-23T01:48:10.000Z (7 days ago)
- Last Synced: 2026-06-23T03:20:41.678Z (7 days ago)
- Topics: ada, ada-2022, arm-cortex-a, arm-cortex-m, containerd, cpp, dev-container, docker, go, kubernetes, linux, rust, ubuntu, zshembedded
- Language: Dockerfile
- Size: 153 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# dev_containers
[](https://github.com/abitofhelp/dev_containers/actions/workflows/docker-build.yml) [](https://github.com/abitofhelp/dev_containers/actions/workflows/docker-publish.yml) [](LICENSE)
Professional development containers for Ada, C++, Go, and Rust — with
embedded (ARM Cortex-M/A) support for Ada, C++, and Rust, including
Teensy 4.1 bare-metal Rust support.
## Available Images
| 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, Teensy 4.1 |
All images are published to GitHub Container Registry:
```text
ghcr.io/abitofhelp/:latest
ghcr.io/abitofhelp/:v
```
### Versioning
All six images share a single repository version. A `vX.Y.Z` tag on this
repo produces the matching `ghcr.io/abitofhelp/: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](CHANGELOG.md) for the
current release and history.
## Quick Start
### 1. Pull an image
```bash
# From your project directory:
make -f ~/containers/dev_containers/ada/Makefile pull-system
```
### 2. Launch a container
```bash
cd ~/Ada/github.com/abitofhelp/my_project
make -f ~/containers/dev_containers/ada/Makefile run-system
```
The 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`.
### 3. Or use the shell aliases
If your `.zshrc` has the convenience functions configured:
```bash
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 |
## Why These Containers Are Useful
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:
```text
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.
## Pre-installed Tools (All Images)
| 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.
### Rust Embedded / Teensy 4.1 Support
The Rust image supports both generic embedded Rust targets and a concrete
PJRC Teensy 4.1 workflow. Teensy 4.1 uses the NXP i.MX RT1062 Cortex-M7, so
Rust projects target `thumbv7em-none-eabihf` and produce Intel HEX files for
PJRC's loader.
| Target | Typical hardware | Runtime shape | Primary output |
|--------|------------------|---------------|----------------|
| `thumbv6m-none-eabi` | Cortex-M0/M0+ | `no_std` bare metal | ELF / BIN / HEX |
| `thumbv7m-none-eabi` | Cortex-M3 | `no_std` bare metal | ELF / BIN / HEX |
| `thumbv7em-none-eabi` | Cortex-M4/M7, soft-float ABI | `no_std` bare metal | ELF / BIN / HEX |
| `thumbv7em-none-eabihf` | Cortex-M4F/M7F, including Teensy 4.1 | `no_std` bare metal | ELF / Intel HEX |
| `thumbv8m.main-none-eabihf` | Cortex-M33-class MCUs | `no_std` bare metal | ELF / BIN / HEX |
| `armv7-unknown-linux-gnueabihf` | ARMv7 Linux SBCs / MPU targets | Linux userspace | Linux executable |
The Teensy 4.1 path includes:
- `cargo-binutils` / `cargo objcopy` for Intel HEX generation.
- `teensy_loader_cli` for PJRC command-line flashing.
- `cargo-generate` plus shell helpers for the upstream `teensy4-rs-template`.
- A smoke example at `rust/examples/teensy41_blink`.
- Detailed notes in [`rust/EMBEDDED.md`](rust/EMBEDDED.md).
Cargo cache behavior: the Rust image stores the pinned toolchain and
preinstalled cargo tools under `/opt`, while runtime Cargo cache and user cargo
installs use `${HOME}/.cargo`. This keeps the image reproducible and avoids
permission problems when building bind-mounted projects as the adapted host
user.
Inside the Rust container:
```bash
cd rust/examples/teensy41_blink
cargo objcopy --release -- -O ihex teensy41_blink.hex
```
Flash from a Linux host/container with USB access:
```bash
teensy_loader_cli --mcu=TEENSY41 -w -v teensy41_blink.hex
```
The container can always build the `.hex`. Direct flashing also requires host
USB access. On Linux, install PJRC's Teensy udev rules on the host. On macOS
with Docker Desktop, build in the container and flash from the macOS host with
the PJRC graphical Teensy Loader or a host-installed `teensy_loader_cli`.
## Reproducibility Policy
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 `*_VERSION` and 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.
## How It Works
### Runtime-Adaptive User Identity
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.
### Container CLI Auto-Detection
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.
### Container Naming
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.
## Deployment Environments
| 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 |
## Linux Host Prerequisites
Before running containers on a headless Ubuntu server (24.04+), complete
these one-time setup steps:
### 1. Allow unprivileged user namespaces (Ubuntu 24.04)
```bash
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'
```
### 2. Install rootless containerd
```bash
containerd-rootless-setuptool.sh install
```
### 3. Install BuildKit (required for `nerdctl build`)
```bash
containerd-rootless-setuptool.sh install-buildkit
```
### 4. Enable linger for your user
```bash
sudo loginctl enable-linger $(whoami)
```
This keeps your systemd session alive across SSH connections so that a
second terminal can see running containers.
### 5. Verify XDG_RUNTIME_DIR
```bash
echo $XDG_RUNTIME_DIR
# Should show: /run/user/
```
If empty, add to your shell profile:
```bash
export XDG_RUNTIME_DIR=/run/user/$(id -u)
```
### 6. Verify
```bash
nerdctl ps # Should return without errors
```
## Repository Layout
```text
dev_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/
```
## Makefile Targets
Run `make -f /Makefile help` for the full list. Common targets:
```bash
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 list
```
For hosts that should explicitly use Docker Desktop, such as macOS, use the
Docker convenience aliases from the repository root:
```bash
make -f rust/Makefile docker-build
make -f rust/Makefile test-docker
make -f rust/Makefile test-teensy41-docker
```
The Docker and Podman aliases stay in the same Makefile invocation as the
language wrapper, so they preserve the `-f /Makefile` context and the
shared repo-root build context.
## Shared Python Launcher
Container launch logic lives in `container_run.py` from the
[hybrid_scripts_python](https://github.com/abitofhelp/hybrid_scripts_python)
repository. The Makefile auto-detects its location by platform. For
standalone use or ad-hoc projects, clone the scripts repo directly:
```bash
# macOS
git clone git@github.com:abitofhelp/hybrid_scripts_python.git \
~/Ada/github.com/abitofhelp/hybrid_scripts_python
# Linux
git clone git@github.com:abitofhelp/hybrid_scripts_python.git \
~/ada/github.com/abitofhelp/hybrid_scripts_python
```
Override the path with the `HYBRID_SCRIPTS_PYTHON` environment variable
if your clone is elsewhere.
## License
BSD-3-Clause — see [LICENSE](LICENSE).
## AI Assistance and Authorship
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.