Part I: The Premise — The Cattle Fallacy & Single-Host Reality

Do you ever catch yourself wishing that Docker containers simply behaved like lightweight virtual machines rather than disposable, single-process microservices?

For over a decade, the cloud-native norm has mandated the pets vs. cattle orthodoxy. Under this model, containers are expected to be immutable, ephemeral, and single-purpose. If you want to adjust an Apache virtual host, test a C compile toolchain, or tweak an environment configuration, the standard answer is heavy: rewrite a Dockerfile, trigger a rebuild, orchestrate multi-container topologies with Compose, and destroy the running instance.

While that paradigm thrives in auto-scaling cloud clusters, it introduces immense operational friction for single-node servers, homelabs, and self-hosted utilities. When hosting stable infrastructure—like a personal Git forge running Forgejo—full hypervisors (KVM, QEMU) carry steep memory and CPU translation overhead. Conversely, the microservice model turns standalone tools into multi-container orchestration sprawl.

Tux-Dock was built to explore the pragmatic middle ground: leveraging the near-zero overhead of Linux namespaces and cgroups, but approaching container lifecycle management through an interactive, persistent system-container model inspired by LXC/LXD.

Repository & Mirrors Access Link
GitHub Primary github.com/MARKMENTAL/tuxdock
MentalNet Self-Hosted Git mentalnet.xyz/forgejo-v2/markmental/tuxdock

Architecture Reality & The Name: Tux + Terminal User Xperience

Before diving into the code, an important architectural clarification: Tux-Dock does not run hardware hypervisors or true virtual machines.

There is no QEMU, KVM, or hardware emulation running underneath. Tux-Dock provisions standard containers that share the host Linux kernel directly. Resource boundaries and process isolation are handled entirely through native kernel primitives: cgroups, namespaces (PID, mount, network, IPC, UTS), and chroot/pivot_root environments. The "micro-VM" terminology reflects the operational user experience—treating the container as a persistent, stateful micro-host rather than a throwaway process—with near-bare-metal performance.

What's in a Name?

When the project started, Tux-Dock was something I made up as an affectionate portmanteau of Tux (the classic Linux penguin mascot) and Docker—a nod to writing lean, native utilities for Linux sysadmins.

As the project evolved into a full functional-reactive terminal interface and I thought more about the name, it naturally took on a fitting dual identity:

  • Tux + Dock: Rooted in core Linux systems engineering principles without extraneous abstractions.
  • T.U.X. Dock: Terminal User Xperience Docker—a keyboard-driven, modal interface designed for direct agency in an SSH session without heavy web dashboards or browser overhead.

Tux-Dock Interface Preview

Tux-Dock C++ Terminal User Interface Screenshot

Figure 1: The FTXUI-powered terminal modal interface navigating active containers and image lifecycles.

Part II: The Journey — From a 77-Line Script to Native C++17 Socket IPC

Tux-Dock began in June 2024 as a humble 77-line Bash script (tux-dock.sh). It presented an interactive CLI menu wrapping standard docker commands to create containers, attach shells, and configure port mappings. It proved the workflow was fast and ergonomic for daily tasks, but quickly hit the hard performance and stability limits of shell scripting.

The Bottlenecks of Shelling Out

As features grew through late 2025 and 2026, executing commands via system() and popen() became unsustainable. Spawning subprocesses for every refresh loop added noticeable fork/exec latency, caused screen flicker, and required fragile string parsing of CLI stdout.

Eliminating Fork/Exec: Direct UNIX Domain Socket IPC

The entire application was rewritten in C++17 using FTXUI for terminal rendering and nlohmann/json for payload serialization. Instead of relying on the official Go SDK or shelling out to the docker-cli binary to check on a container's status, Tux-Dock communicates directly with the Docker Engine API over /var/run/docker.sock, while the docker-cli functionality is reserved to only where it is needed, for container creation and container interaction commands.

  • Zero Subprocess Overhead: Bypasses CLI binary invocations, eliminating argument parsing and process initialization cycles.
  • Bypassing the TCP Stack: Avoids 127.0.0.1 loopback network stack traversal, routing tables, and socket port allocation. Communication is pure in-memory POSIX IPC buffer streaming.
  • The Daemon as the Database: Tux-Dock stays completely stateless. It does not need SQLite or a cache layer; the Docker daemon serves as the real-time, authoritative single source of truth.
Evolutionary Phase Core Stack Communication Channel State Tracking Strategy
Phase 1 (June 2024) Bash Shell Script Subprocess calls to docker-cli Ad-hoc text scraping from CLI stdout
Phase 2 (Late 2025) Monolithic C++17 CLI popen() and system() wrappers Stringstream parsing of stdout
Phase 3 (Early 2026) C++17 + FTXUI TUI fork() and execvp() process runners Modal orchestration over C++ structs
Phase 4 (v0.1.1-beta) C++17 + Direct UNIX Socket Raw HTTP/1.1 client over /var/run/docker.sock Authoritative JSON payloads deserialized into native models

Under the Hood: Low-Level C++ Components

main.cpp (TuxDockApp modal orchestration & FTXUI rendering loop) src/ ├── docker_manager.cpp Container & image lifecycle management ├── docker_engine_client.cpp Raw HTTP/1.1 socket client over /var/run/docker.sock ├── http_response_parser.cpp Handles chunked transfer encoding, 204/304 statuses, & timeouts ├── container_parser.cpp Docker Engine API JSON -> ContainerInfo mapping ├── process_runner.cpp POSIX fork/exec with pipe capture & interactive TTY handoffs ├── operation_state.cpp Thread-safe busy modal state tracking & animation frames └── stop_waiter.cpp Polled container shutdown verification with retry logic tests/ ├── Unit tests (HTTP parser, container JSON parser, stop sequences) └── Integration tests (Automated Docker socket smoke tests & TUI transcript playback)

Part III: Operational Reality — Manual Maintenance vs. Unattended Automation

Adopting the system-container model shifts operational trade-offs. It gives you the longevity of a persistent environment without the hypervisor tax, but requires understanding how interactive sysadmin maintenance and background automation work together.

Operational Aspect Traditional Cloud-Native Containers Tux-Dock Shared-Kernel Micro-VM Model
Kernel Boundary Shared host kernel (ephemeral process namespace). Shared host kernel (persistent system namespace).
PID 1 Architecture Direct application binary (terminates container on exit). Persistent keepalive (sleep infinity), keeping container active.
State & Persistence Strictly immutable; all state offloaded to external volumes/DBs. Stateful rootfs; installed packages and local configs persist.
Maintenance Burden Re-run automated CI pipelines and rebuild base images. Standard sysadmin upkeep (package upgrades, log rotation, backups).
Process Execution Single declared entrypoint. Interactive shell attachment or background detached command execution.

Manual Sysadmin Upkeep

Because the container rootfs is persistent, you treat the container like a lean virtual private server. You shell in (Attach Shell to Running Container), run distribution package updates (e.g. apt-get update && apt-get upgrade), configure files in /etc, and manage running services. While this requires manual sysadmin responsibility, it eliminates the overhead of managing complex multi-stage Dockerfiles for simple, stable workloads.

Unattended Automation: Running Detached Scripts

Manual care does not preclude automation. Tux-Dock provides first-class support for executing background workloads via Menu Action 10: Run Detached Command in Container.

You can write initialization, backup, or maintenance scripts on your host or inside the container, triggering them asynchronously via non-blocking exec sessions:

# Example: Triggering a background maintenance script via Tux-Dock detached execution /bin/sh -c "/app/scripts/backup-forgejo.sh > /var/log/backup.log 2>&1 &" # The command executes inside the isolated namespace without blocking the TUI or holding a shell open.

This provides a clean operational balance: interactive terminal access when configuring services, and detached execution when long-running jobs need to run unattended.

Part IV: The Pipeline — Hardened POSIX Tooling & Zero Dependencies

A minimalist systems tool should not rely on heavy CI/CD frameworks. Tux-Dock's compilation, testing, and reporting infrastructure is implemented entirely within a portable POSIX /bin/sh script (compile.sh).

Hardware & Cgroup-Aware Resource Throttling

Compiling C++ templates on resource-constrained 512MB–1GB VPS nodes frequently triggers the Linux Out-Of-Memory (OOM) killer. compile.sh inspects both cgroup v1 and v2 memory limits (/sys/fs/cgroup/memory.max, memory.limit_in_bytes) alongside /proc/meminfo. If low RAM is detected, it automatically restricts build concurrency to -j1 and applies -DCMAKE_BUILD_TYPE=MinSizeRel.

Live Netcat CI Test Reports in HTML 3.2 Final

When run with --web-test-view, the build driver executes the full ctest suite, parses test transcripts using standard awk and sed, generates a structured HTML 3.2 Final report, and streams it over a raw socket using standard netcat / nc / ncat:

./compile.sh --web-test-view 8095 # Test report: http://0.0.0.0:8095/ # Zero external web servers, zero Python/Node dependencies — pure Unix pipeline

Test Suite Web View Preview

HTML 3.2 Test Report Browser Screenshot

Figure 2: The HTML 3.2 test report served directly via Netcat, displaying CTest metrics and integration transcripts.

Building and Getting Started

Tux-Dock requires only a standard C++17 compiler, CMake 3.16+, and read/write access to /var/run/docker.sock.

Step 1 — Clone the Repository

git clone https://github.com/MARKMENTAL/tuxdock.git cd tuxdock

Step 2 — Compile and Test

Compile the binary and execute unit and integration test suites:

./compile.sh

Compile the binary along with the HTML 3.2 web view of the testing suite:

./compile.sh --web-test-view

Or build the binary directly without running the test suite:

./compile.sh --no-test

Step 3 — Run Tux-Dock

sudo ./build/tux-dock

Tip: To run Tux-Dock without sudo, ensure your Linux user belongs to the docker group.