Skip to content

Dockermint/dockermint

Dockermint

Dockermint

The first CI/CD Pipeline for Cosmos SDK.


What is Dockermint?

Building and maintaining Docker images for blockchain nodes is repetitive work: tracking upstream releases, writing multi-stage Dockerfiles, managing cross-compilation for amd64 and arm64, pushing to a registry, and keeping everything consistent across a fleet of chains. Teams usually end up with a collection of bespoke shell scripts that diverge over time.

Dockermint replaces that with a single, uniform pipeline. Define a chain once in a TOML recipe file and Dockermint handles the rest — Dockerfile generation, cross-compilation via BuildKit, release polling, registry push, persistence, and notifications. Adding a new chain requires no code changes: only a new recipe file.

Who is Dockermint for? Infrastructure engineers and DevOps teams running Cosmos SDK validator nodes or RPC infrastructure who need reproducible, multi-architecture Docker images without maintaining a separate build system per chain.

Status: Phase 1 complete. dockermint-cli is fully implemented and runnable. Daemon mode (Phase 2) and gRPC/RPC mode (Phase 3) are not yet implemented.


Resources

Resource URL
Main site https://dockermint.io
Documentation https://docs.dockermint.io/
GitHub https://github.com/Dockermint/dockermint
Related project Pebblify

Operating Modes

Dockermint runs in three modes depending on your use case:

  • CLI — one-shot build, locally or via a remote BuildKit endpoint. Errors cause an immediate dump, log, and exit. Implemented in Phase 1.
  • Daemon — continuous polling for new GitHub releases. On error: log, notify, persist the failure, and continue polling. Phase 2 (not yet implemented).
  • RPC — daemon with an optional gRPC server, accepting remote build requests from a CLI client. On error: log and return idle. Phase 3 (not yet implemented).

Prerequisites

  • Docker with the BuildKit plugin (docker buildx version)
  • QEMU user-space emulation for multi-arch builds:
    docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    
  • Rust toolchain (if building from source)

Build and Install

# Build both binaries with the default feature set (OpenSSL vendored)
cargo build --release

# Switch to rustls instead of OpenSSL
cargo build --release --no-default-features --features ssl-rustls

# Binaries produced:
#   target/release/dockermint-cli      (fully implemented)
#   target/release/dockermint-daemon   (stub — not yet implemented)

CLI Usage

dockermint-cli provides four subcommands.

build

Execute a build for one or more recipes.

dockermint-cli build --recipe <name|all> [OPTIONS]
Flag Type Default Description
--recipe <name|all> string required Recipe name (e.g. cosmos-gaiad) or all
--version <tag> string latest release Specific version tag to build
--flavor KEY=VALUE repeatable recipe default Override a flavor dimension
--push flag off Push to the registry after a successful build
--force flag off Rebuild even if the tag already exists in the registry
--platform <linux/amd64|linux/arm64> repeatable all configured Target platform(s)
--dry-run flag off Resolve and print the Dockerfile; do not build
--keep-builders flag off Keep BuildKit builder instances after the build
--config <path> path ./config.toml Path to config.toml
--recipes-dir <path> path from config Path to the recipes directory
--log-level <level> string from config Log level: trace, debug, info, warn, error

When --recipe all is used, every discovered recipe is built in sequence. Failures are recorded per-recipe and the process continues. The exit code is the highest severity code among all builds.

When --version is omitted, the latest GitHub release for the recipe's repository is fetched automatically.

--dry-run is offline: it resolves and prints the Dockerfile to stdout and the resolved flavor summary to stderr, without contacting a Docker daemon or the VCS. When --version is also omitted, a <latest> placeholder stands in for the version tag.

When --flavor is repeated with the same key, the last value wins.

Examples

# Build the latest gaiad release for all configured platforms
dockermint-cli build --recipe cosmos-gaiad

# Build a specific version with PebbleDB
dockermint-cli build --recipe cosmos-gaiad --version v19.0.0 --flavor db_backend=pebbledb

# Build all recipes and push to the registry
dockermint-cli build --recipe all --push

# Dry-run: print the generated Dockerfile without building
dockermint-cli build --recipe cosmos-gaiad --dry-run

# Build for a single platform only
dockermint-cli build --recipe cosmos-gaiad --platform linux/amd64

# Use a custom config and recipes directory
dockermint-cli build --recipe cosmos-gaiad \
  --config /etc/dockermint/config.toml \
  --recipes-dir /etc/dockermint/recipes

list-recipes

List all discovered recipes.

dockermint-cli list-recipes [--recipes-dir <path>] [--config <path>] [--format table|json]

Default output format is an aligned table. Use --format json for machine-readable output.

list-flavors

List available flavor dimensions for a recipe.

dockermint-cli list-flavors --recipe <name> [--recipes-dir <path>] [--config <path>] [--format table|json]

Shows every declared flavor dimension with its available values and the currently resolved default.

version

Print version and build information.

dockermint-cli version

Outputs the binary name, crate version, git commit hash, target triple, and compiled feature flags.


Configuration

Dockermint is configured via config.toml. All sections use strict parsing: unknown keys are rejected at startup to catch typos. CLI arguments take precedence over config.toml values.

Secrets must never appear in config.toml. Use .env for all sensitive values (see Secrets below).

config.toml reference

# ============================================================================
# [meta] — required; schema versioning
# ============================================================================
[meta]
config_version = 1          # Schema version of this file (required)

# ============================================================================
# [general] — cross-cutting operational settings
# ============================================================================
[general]
mode = "cli"                # "cli" | "daemon"
log_level = "info"          # "trace" | "debug" | "info" | "warn" | "error"
log_dir = "/var/log/dockermint"   # Directory for rotated log files
recipes_dir = "./recipes"   # Path to the recipes directory

# ============================================================================
# [daemon] — polling settings (daemon mode only; Phase 2)
# ============================================================================
[daemon]
poll_interval_secs = 300    # Global default: seconds between VCS polls

# Per-chain poll-interval override:
# [daemon.chains."cosmos-gaiad"]
# poll_interval_secs = 600

# ============================================================================
# [grpc] — gRPC server settings (RPC mode only; Phase 3)
# ============================================================================
[grpc]
enabled = false
listen_address = "0.0.0.0:50051"
auth_mode = "token"         # "token" | "mtls" | "both"
# tls_cert_path = "/path/to/server.crt"
# tls_key_path  = "/path/to/server.key"
# tls_ca_path   = "/path/to/ca.crt"
# Token secret: GRPC_AUTH_TOKEN in .env

# ============================================================================
# [builder] — image build settings
# ============================================================================
[builder]
platforms = ["linux/amd64", "linux/arm64"]   # Target build platforms
# docker_host = "unix:///var/run/docker.sock"  # Empty = system default
# cleanup_builders = false                     # Destroy builders after build

# ============================================================================
# [registry] — OCI registry settings
# Credentials: REGISTRY_USER, REGISTRY_PASSWORD in .env
# ============================================================================
[registry]
url = ""                    # e.g. "ghcr.io/dockermint". Empty = Docker Hub.

# ============================================================================
# [notifier] — notification settings (daemon mode only; Phase 2)
# Secrets: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID in .env
# ============================================================================
[notifier]
enabled = false             # Enable/disable build-status notifications

# ============================================================================
# [metrics] — Prometheus scrape endpoint
# ============================================================================
[metrics]
enabled = true
listen_address = "0.0.0.0:9100"   # Prometheus scrape endpoint bind address

# ============================================================================
# [flavours] — flavor overrides
# Global overrides apply to every recipe.
# Per-recipe overrides live under [flavours.recipes."<recipe-stem>"].
# ============================================================================
[flavours]
# db_backend = "pebbledb"   # global override example

# [flavours.recipes."cosmos-gaiad"]
# db_backend = "pebbledb"

Secrets

All sensitive values are loaded from .env (via dotenvy). A missing .env file is not an error; only a malformed one is. Variables are optional at load time; required-ness is enforced at startup based on which features are in use.

Variable Used by Required when
GH_PAT VCS scrapper Authenticated GitHub API requests (raises rate limit from 60 to 5000 req/hr)
GH_USER VCS scrapper Paired with GH_PAT for authenticated requests
REGISTRY_USER Registry push --push flag is used
REGISTRY_PASSWORD Registry push --push flag is used
TELEGRAM_BOT_TOKEN Notifier [notifier] enabled = true (Phase 2)
TELEGRAM_CHAT_ID Notifier [notifier] enabled = true (Phase 2)
GRPC_AUTH_TOKEN gRPC server [grpc] auth_mode = "token" or "both" (Phase 3)

Example .env file:

GH_PAT=ghp_xxxxxxxxxxxxxxxxxxxx
GH_USER=your-github-username
REGISTRY_USER=your-registry-user
REGISTRY_PASSWORD=your-registry-password

Exit Codes

Code Name Condition
0 EXIT_SUCCESS Operation completed successfully
1 EXIT_GENERAL Unclassified error
2 EXIT_CONFIG Configuration file missing, invalid, or version mismatch
3 EXIT_RECIPE Recipe file missing, invalid, or incompatible flavors
4 EXIT_SYSTEM System prerequisites not met (Docker, BuildKit, disk, etc.)
5 EXIT_VCS VCS API error (auth, rate limit, network, version not found)
6 EXIT_BUILD Docker/BuildKit build failed
7 EXIT_PUSH Registry push failed (auth, network, manifest)
8 EXIT_STORE Database error (save, read, schema mismatch)
9 EXIT_NOTIFY Notification send failed
10 EXIT_INTERNAL Internal bug (unreachable code, invariant violation)

For --recipe all, the exit code is the highest severity (numerically greatest) code among all per-recipe builds. EXIT_SUCCESS (0) is returned only when every build succeeded.


Feature Flags

The default feature set activates all production modules with vendored OpenSSL for TLS. Modules are replaceable at compile time via Cargo feature flags.

Default features

ssl-openssl, vcs-github, builder-buildkit, registry-oci,
db-redb, notifier-telegram, metrics-prometheus

TLS backend (mutually exclusive — exactly one required)

Feature Description
ssl-openssl Vendored OpenSSL via reqwest/native-tls-vendored. Default. Builds OpenSSL from source; no system OpenSSL dependency.
ssl-rustls rustls via reqwest/rustls. No OpenSSL; uses aws-lc-rs as crypto provider.

To switch: cargo build --no-default-features --features ssl-rustls,...

Module backends

Feature Module Description
vcs-github Scrapper GitHub REST API client for tag/release discovery
builder-buildkit Builder BuildKit via docker buildx; manages per-platform builder instances
registry-oci Push OCI Distribution Spec HTTP client for authentication and image push
db-redb Saver Embedded redb key-value store for build state persistence
notifier-telegram Notifier Telegram Bot API notifications for build events
metrics-prometheus Metrics Prometheus text-exposition scrape endpoint

Test-only gate

Feature Description
docker-integration-tests Enables integration tests that require a live Docker daemon. Not part of the default set.

At least one backend in each category must be compiled in. The build will fail with a clear compile_error! if a category has no active backend.


Supported Chains

Phase 1 targets

Chain Binary Sidecars
Cosmos Hub gaiad
Axelar axelard Tofnd, Vald
Fetch fetchd
Injective injectived Peggo
Osmosis osmosisd

Current recipes (available now)

Chain Binary Recipe file
Cosmos Hub gaiad recipes/cosmos-gaiad.toml
Kyve kyved recipes/kyve-kyved.toml

Flavor System

Recipes declare available and default flavors per dimension:

[flavours.available]
db_backend    = ["goleveldb", "pebbledb"]
binary_type   = ["dynamic", "static"]
running_env   = ["alpine3.23", "resolute", "distroless"]
running_user  = ["root", "custom", "dockermint"]
build_tags    = ["netgo", "ledger", "muslc"]

[flavours.default]
db_backend    = "goleveldb"
binary_type   = "static"
running_env   = "alpine3.23"
running_user  = "root"
build_tags    = ["netgo", "muslc"]

The active flavor for any dimension is resolved in priority order:

CLI --flavor args  >  config.toml per-recipe  >  config.toml global  >  recipe defaults

Incompatible flavor combinations produce an error before the build starts.


Architecture

dockermint-cli          dockermint-daemon
(one-shot build)        (polling + optional gRPC server)
       |                       |
       +----------+------------+
                  |
         SHARED CORE PIPELINE
                  |
  config -> checker -> recipe -> scrapper
                                    |
  push <- builder <- builder <- builder
               (buildx)  (Dockerfile)  (template engine)
       |
  saver / notifier / metrics

  CROSS-CUTTING: logger, commands

Module summary

Module Responsibility
config Load and merge config.toml, .env, and CLI args
checker Verify Docker, BuildKit, disk, and network prerequisites
recipe Parse TOML recipes, resolve flavors, validate compatibility
scrapper GitHub API client: fetch tags and releases
builder Template engine, BuildKit manager, Go recipe builder
push OCI registry authentication and image push
saver Build state persistence (RedB, embedded)
notifier Build status notifications (default: Telegram)
metrics Prometheus metrics server
cli Clap-based CLI with subcommands and exit code mapping
logger Structured logging with log rotation
commands Shell command execution shared by all modules

Template engine variables

Dockerfile content is produced by a template engine that resolves two classes of variables from the recipe:

  • {{UPPERCASE}} — host variables injected by Dockermint (e.g. {{HOST_ARCH}}, {{SEMVER_TAG}}, {{CREATION_TIMESTAMP}}, {{BUILD_TAGS_COMMA_SEP}})
  • {{lowercase}} — build variables resolved at build time from recipe [variables], flavors, and profiles (e.g. {{golang_version}}, {{db_backend}})

Project Status

Phase Target Scope Status
0 N/A Architecture specs (all modules) Complete
1 v0.1.0 CLI mode, 5 chains, BuildKit, OCI push Complete
2 v0.2.0 Daemon mode, persistence, metrics, notifier Planned
3 v0.3.0 gRPC server and authenticated CLI client Planned
4 v1.0.0 Chain expansion, C-FFI library, security audit Planned

See docs/ROADMAP.md for the full phase breakdown.


Contributing

Dockermint follows a design-first engineering workflow. Every feature begins with an architecture spec that is reviewed and confirmed before any code is written. A GitHub issue is opened to track the work, code is implemented against the spec, and the change must pass the full test suite — including mutation testing — before a pull request is opened. Code review is required before merge. No step may be skipped.

See docs.dockermint.io/contributing or docs/ in this repository for the full contribution guide.


Compilation Targets

Dockermint compiles and runs on all five toolchains:

Target
x86_64-unknown-linux-gnu
x86_64-unknown-linux-musl
aarch64-unknown-linux-gnu
aarch64-unknown-linux-musl
aarch64-apple-darwin

License

Apache License, Version 2.0. See LICENSE.

Releases

No releases published

Packages

 
 
 

Contributors

Languages