Distrobox Go Rewrite: Architecture and Design

This document describes the architecture of the Distrobox Go rewrite, explaining how different layers interact and the design decisions behind the codebase. It’s meant to help contributors understand the system and know where to make changes.

Overview

The Distrobox Go rewrite is designed with clear separation of concerns in mind. The rewrite followed these principles:

Directory Structure

distrobox
├── cmd/distrobox/
│   └── main.go                          # Entry point
├── internal/
│   ├── cli/                             # CLI layer (command definitions)
│   │   ├── root.go                      # Root command with global flags
│   │   ├── create.go, list.go, etc.     # Individual commands
│   │   └── helpers.go
│   ├── config/                          # Configuration management
│   ├── inside-distrobox/                 
│   │   └── assets/                      # Embedded shell scripts
├── pkg/
│   ├── commands/                        # Business logic layer
│   │   ├── create.go, list.go, etc.     # Command implementations
│   ├── containermanager/                # Container abstraction
│   │   ├── containermanager.go          # Interface definitions
│   │   └── providers/                   # Implementations
│   │       ├── podman.go
│   │       └── docker.go
│   ├── ui/                              # UI components
│   │   ├── progress.go
│   │   ├── printer.go
│   │   └── prompt.go
│   └── manifest/                        # Manifest parsing

Architecture Layers

1. CLI Layer (internal/cli/)

The CLI layer handles command-line argument parsing, global flag processing, and command dispatch. It is also responsible for binding the application to the shell’s stdin/stdout, for loading the configuration, and for instantiating the components.

Among other things, the concerete ContainerManager implementation is selected and instantiated in the cli layer.

2. Command Layer (pkg/commands/)

The command layer contains the business logic for each distrobox operation. Commands are independent of CLI specifics and can be tested and reused independently.

Commands should be pure orchestrators. They coordinate between the container manager abstraction and UI components, but don’t contain low-level implementation details.

Each command is implemented by a Execute method that takes a context and options struct.

3. Container Manager Layer (pkg/containermanager/)

The container manager is the abstraction over different container runtimes. This design allows distrobox to work with Docker, Podman, and other container managers without duplicating logic.

4. UI Layer (pkg/ui/)

The UI layer provides simple components for user interaction and output formatting. These are instantiated in the CLI layer and passed to commands.

Dependency Injection Pattern

The architecture uses context-based dependency injection to pass the container manager from the root command to all subcommands.

Flow:

main()
  ↓
LoadConfig()
  ↓
NewRootCommand().Run()
  ↓
beforeAction() [global hooks]
  ↓ Creates container manager
  ↓ Stores in context
  ↓
Specific command action (e.g., createAction)
  ↓ Extracts container manager from context
  ↓ Creates UI tools
  ↓ Delegates to command layer

This pattern ensures:

Configuration System

Configuration is loaded once at startup in main(). Configuration sources (in order of precedence):

  1. Command-line flags
  2. Environment variables (prefixed with DBX_)
  3. Config file (~/.config/distrobox/distrobox.conf)
  4. Defaults

This centralized approach makes it easy to understand where values come from and ensures consistency across commands.

Shell Scripts

When a container is created, part of the Distrobox application is loaded in the container as it is meant to be executed inside it:

Such commands are POSIX shell scripts that are included as assets in internal/inside-distrobox/assets