Home Problem How It Works Get Started Technical Deep Dive GitHub Repo
Under active development contributions welcome!

Technical Deep Dive

Explore the architecture, build pipeline, and technical details that make ContainifyCI work.

See it in action

Here's how dunebot a real ContainifyCI project defines its entire build and CI pipeline.

engine-ci building itself
Animated demo showing engine-ci building the engine-ci project locally
1. Define your build .containifyci/containifyci.go
package main

import (
    "os"

    "github.com/containifyci/engine-ci/client/pkg/build"
    "github.com/containifyci/engine-ci/protos2"
)

func main() {
    os.Chdir("../")
    opts := build.NewGoServiceBuild("dunebot")
    opts.Application = "dunebot"
    opts.File = "main.go"
    opts.Properties = map[string]*build.ListValue{
        "goreleaser": build.NewList("true"),
    }
    build.Serve(opts)
}
2. Wire it into GitHub Actions .github/workflows/pull-request.yaml
name: Pull Request
on:
  pull_request:
    branches: [main]

jobs:
  build:
    name: Build
    uses: containifyci/.github/.github/workflows/pull-request.yml@v1
    secrets: inherit
3. Add a release workflow .github/workflows/release.yaml
name: Release
on:
  push:
    branches: [main]
    paths-ignore: ['.github/**']

jobs:
  build-and-release:
    uses: containifyci/.github/.github/workflows/release.yml@v1
    secrets: inherit

One Go file for your build logic. Two tiny YAML files to connect it to GitHub Actions. The same engine-ci run command works locally and in CI. There are no divergence and no surprises.

See the full working example at containifyci/dunebot.

Why Go instead of YAML?

YAML Pipelines Go Pipelines (ContainifyCI)
Error detection Runtime fails mid-build Compile time fails before build starts
Testing Manual / none Unit tests with go test
IDE support Syntax highlighting only Full autocomplete, refactoring, go-to-definition
Reuse Copy-paste snippets Import packages, call functions
Debugging Push and pray Run locally, set breakpoints

Architecture

Your code (.containifyci/containifyci.go)
engine-ci CLI → Compiles to single binary
Container runtime (Docker / Podman)
Isolated build steps with pinned tool versions

engine-ci reads your build definition from .containifyci/containifyci.go, compiles it into a self-contained binary, and orchestrates containers to execute each build step in isolation. Tool versions are pinned inside the containersa and not on the host machine.

Language support is modular. engine-java and engine-python are extensions that plug into the core. Adding support for a new language means writing a Go package that implements the build interface.

Build Pipeline

Every build runs through 7 orchestrated phases. Quality and Publish run concurrently for faster feedback.

Auth
PreBuild
Build
PostBuild
Quality async
Apply
Publish async

Container Runtimes

🐳

Docker

  • Multi-arch builds via buildx
  • Platform-specific image pulling
  • Registry authentication
🦭

Podman

  • Rootless containers
  • Native secrets support
  • Buildah integration
  • Manifest-based multi-arch

Auto-detection picks the available runtime, or override with the CONTAINER_RUNTIME env var.

Container Images

engine-ci builds and publishes its own container images to Docker Hub. Each build step runs inside a purpose-built image with pinned tool versions. The images themselves are built by engine-ci.

📦 Checksum-Based Tags

Each Dockerfile is hashed at compile time. The SHA256 checksum becomes the image tag, so a rebuilt image is only pushed when the Dockerfile actually changes.

⚙️ Compile-Time Metadata

Go's go:generate extracts Dockerfile content, base image versions, and checksums into generated Go code. There are no runtime parsing, errors caught before the build starts.

🏗️ Multi-Arch Builds

All images are built for both linux/amd64 and linux/arm64, so the same pipeline works on Intel and Apple Silicon machines.

🔄 Layered Image Strategy

Intermediate build images contain compilers and tooling. Production images are minimal (Alpine-based). Only the compiled binary and a non-root user.

Image Purpose Base
containifyci/golang-* Go build with linting (Alpine, Debian, CGO variants) golang:1.25-alpine / golang:1.25
containifyci/maven-* Java & Maven builds with TestContainers eclipse-temurin JDK
containifyci/python-* Python build, lint, and packaging python:3.14-slim-bookworm
containifyci/sonar SonarCloud scanner (multi-arch) alpine:3.20

All images are hosted at hub.docker.com/u/containifyci. engine-ci pulls or builds them automatically. There is no need for manual image management.

Secure Secrets

Secrets never touch disk. An in-memory key-value store with a REST API keeps credentials safe throughout the build lifecycle.

🗄️ In-Memory KV Store

HTTP REST API: secrets live only in memory, never written to disk or in the container metadata or logs

🔐 HMAC-SHA256 Auth

Request signing with timestamps and nonces prevents replay attacks

🔗 Value Resolution

mem: KV store / cmd: shell command / env: environment variable

⏱️ Session Tokens

Per-session tokens with 1-hour expiry and constant-time comparison

SSH Agent Forwarding

Unix socket bind mounts forward your host SSH agent into build containers. This enables private repository access during builds. Platform-aware for both Linux and macOS, and available across all build types.

Key Integrations

🐳 Docker 🦭 Podman 🔍 Trivy 📦 GoReleaser 🔑 Feller 🤖 Dunebot ⚙️ GitHub Actions 🦊 GitLab CI 🔐 SSH Forwarding 🏗️ Multi-Arch Builds 🔎 SonarCloud 📜 Protobuf ☁️ Pulumi