Skip to content

dievilz/ddc

Repository files navigation

DDC — Dievilz Deploy Control

A deterministic, policy-driven CI/CD action executor

DDC replaces complex shell scripts, ad-hoc deployers, and CI YAML logic with a single, explicit, deterministic binary designed for real production environments.

Why DDC?

  • Single Binary: One executable, no dependencies
  • CI Agnostic: Works with GitLab CI, GitHub Actions, Jenkins, or any CI system
  • Policy-Driven: Explicit control over retries, fallbacks, and failure handling
  • Deterministic: Same inputs always produce same outputs
  • Production-Ready: Designed for unreliable infrastructure and partial failures
  • Auditable: Machine-readable JSON results for every execution

Philosophy

Best-Effort Simplicity
As simple as possible. As complete as necessary. No hidden behavior.

DDC is not a CI system, workflow engine, or orchestrator. It is a deployment control tool that CI systems invoke to perform specific operational actions.

Installation

Build from Source

git clone https://github.com/dievilz/ddc.git
cd ddc
go build -o ddc ./cmd/ddc

Download Binary

# Download the latest release
curl -L https://github.com/dievilz/ddc/releases/latest/download/ddc-linux-amd64 -o ddc
chmod +x ddc

Quick Start

1. Publish a Docker Image

# Configure registry
export REGISTRY_PROD_URL=registry.example.com
export REGISTRY_PROD_USERNAME=myuser
export REGISTRY_PROD_PASSWORD=mypassword

# Configure image
export IMAGE_NAME=myapp
export IMAGE_TAG=v1.0.0

# Execute
ddc run publish-image

2. Deploy Docker Compose Stack

# Configure SSH target
export SSH_PROD_HOST=server.example.com
export SSH_PROD_USER=deploy
export SSH_PROD_KEY=/path/to/ssh/key

# Configure deployment
export COMPOSE_FILE=docker-compose.yml
export PROJECT_NAME=myapp
export REMOTE_DIR=/opt/myapp

# Execute
ddc run deploy-compose

Core Concepts

Actions

An Action represents a high-level operational intent:

  • publish-image — Publish Docker images to registries
  • deploy-compose — Deploy Docker Compose stacks via SSH

Actions are explicit, validated, and deterministic.

Capabilities

A Capability is a concrete resource an action can use:

  • Docker registries
  • SSH hosts
  • Docker daemons
  • File system paths

Capabilities are discovered from environment variables and explicitly tested for availability.

Policy

A Policy defines how an action uses its capabilities:

  • Minimum Success: How many successful executions are required
  • Fail Fast: Stop after first failure or try all capabilities
  • Retries: How many times to retry each capability
  • Priority Order: Which capabilities to try first

Policy is data, not control flow.

Executor

The Executor orchestrates the entire process:

  1. Detects available capabilities
  2. Validates action inputs
  3. Executes the action according to policy
  4. Tracks successes and failures
  5. Produces logs and machine-readable results

Configuration

Capability Configuration

Docker Registries

Configure one or more registries using environment variables:

# Primary registry
export REGISTRY_PRIMARY_URL=registry.example.com
export REGISTRY_PRIMARY_USERNAME=user
export REGISTRY_PRIMARY_PASSWORD=pass

# Backup registry
export REGISTRY_BACKUP_URL=backup-registry.example.com
export REGISTRY_BACKUP_USERNAME=user
export REGISTRY_BACKUP_PASSWORD=pass

SSH Hosts

Configure SSH targets:

export SSH_PROD_HOST=prod.example.com
export SSH_PROD_USER=deploy
export SSH_PROD_PORT=22
export SSH_PROD_KEY=/path/to/key

export SSH_STAGING_HOST=staging.example.com
export SSH_STAGING_USER=deploy

Policy Configuration

Control execution behavior:

# Require at least 2 successful executions
export DDC_MIN_SUCCESS=2

# Stop after first failure
export DDC_FAIL_FAST=true

# Retry failed attempts up to 3 times
export DDC_MAX_RETRIES=3

# Try capabilities in specific order
export DDC_PRIORITY_ORDER=registry-primary,registry-backup

# Custom result file location
export DDC_RESULT_FILE=./build/ddc-result.json

Action-Specific Variables

publish-image

export IMAGE_NAME=myapp          # Required: Image name
export IMAGE_TAG=v1.0.0          # Required: Image tag

deploy-compose

export COMPOSE_FILE=docker-compose.yml   # Required: Path to compose file
export PROJECT_NAME=myapp                # Optional: Project name (default: app)
export REMOTE_DIR=/opt/myapp             # Optional: Remote directory
export ENV_FILE=.env.production          # Optional: Environment file to transfer

Output

Exit Codes

Code Meaning
0 Success — Policy satisfied
1 Policy not met — Not enough successful executions
2 Invalid configuration — Missing required inputs
3 Capability failed — No available capabilities
4 Action failed — Execution error
5 Validation failed — Input validation error

Logs

DDC produces human-readable, phase-based logs:

[+0.001s] PHASE: Initializing
[+0.002s] INFO: Action: publish-image
[+0.003s] PHASE: Detecting Capabilities
[+0.010s] INFO: Discovered 2 capabilities
[+0.011s] INFO:   ✓ registry-primary [docker-registry] - available
[+0.012s] INFO:   ✓ registry-backup [docker-registry] - available
[+0.013s] PHASE: Executing Action: publish-image
[+0.014s] INFO: Validation passed
[+0.015s] INFO: Found 2 available capabilities
[+0.016s] INFO: Attempting capability: registry-primary (type: docker-registry)
[+2.341s] INFO: ✓ Success with registry-primary (duration: 2.325s)
[+2.342s] INFO: Policy satisfied: 1/1 successful executions
[+2.343s] PHASE: Execution Complete: SUCCESS

Result File

Every execution produces a machine-readable JSON result:

{
  "ActionName": "publish-image",
  "StartTime": "2024-01-15T10:30:00Z",
  "EndTime": "2024-01-15T10:30:02Z",
  "TotalDuration": 2340000000,
  "CapabilitiesEvaluated": ["registry-primary"],
  "Attempts": [
    {
      "CapabilityName": "registry-primary",
      "Success": true,
      "Error": "",
      "Attempt": 1,
      "Duration": 2325000000,
      "Metadata": {
        "image_ref": "registry.example.com/myapp:v1.0.0",
        "registry": "registry.example.com",
        "pushed_at": "2024-01-15T10:30:02Z"
      }
    }
  ],
  "SuccessCount": 1,
  "FailureCount": 0,
  "PolicySatisfied": true,
  "Decision": "SUCCESS",
  "Reason": "Policy satisfied: 1/1 successful executions",
  "Artifacts": {
    "image_ref": "registry.example.com/myapp:v1.0.0",
    "registry": "registry.example.com"
  }
}

CI/CD Integration

GitLab CI

publish:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache docker-cli
    - wget -O /usr/local/bin/ddc https://github.com/dievilz/ddc/releases/latest/download/ddc-linux-amd64
    - chmod +x /usr/local/bin/ddc
  script:
    - ddc run publish-image
  artifacts:
    reports:
      dotenv: ddc-result.json

GitHub Actions

- name: Publish Image
  env:
    REGISTRY_PROD_URL: ${{ secrets.REGISTRY_URL }}
    REGISTRY_PROD_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
    REGISTRY_PROD_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
    IMAGE_NAME: myapp
    IMAGE_TAG: ${{ github.sha }}
  run: |
    curl -L https://github.com/dievilz/ddc/releases/latest/download/ddc-linux-amd64 -o ddc
    chmod +x ddc
    ./ddc run publish-image

Jenkins

stage('Deploy') {
  steps {
    sh '''
      curl -L https://github.com/dievilz/ddc/releases/latest/download/ddc-linux-amd64 -o ddc
      chmod +x ddc
      ./ddc run deploy-compose
    '''
  }
}

Advanced Usage

Multiple Registries with Fallback

# Configure multiple registries
export REGISTRY_PRIMARY_URL=registry1.example.com
export REGISTRY_PRIMARY_USERNAME=user
export REGISTRY_PRIMARY_PASSWORD=pass

export REGISTRY_SECONDARY_URL=registry2.example.com
export REGISTRY_SECONDARY_USERNAME=user
export REGISTRY_SECONDARY_PASSWORD=pass

# Require at least one success
export DDC_MIN_SUCCESS=1

# Try primary first, fallback to secondary
export DDC_PRIORITY_ORDER=registry-primary,registry-secondary

# Execute
ddc run publish-image

Redundant Deployment

# Configure multiple hosts
export SSH_PROD1_HOST=prod1.example.com
export SSH_PROD1_USER=deploy

export SSH_PROD2_HOST=prod2.example.com
export SSH_PROD2_USER=deploy

# Require both to succeed
export DDC_MIN_SUCCESS=2

# Execute
ddc run deploy-compose

Verbose Logging

ddc run publish-image --verbose
# or
ddc run publish-image -v

Development

Project Structure

ddc/
├── cmd/ddc/              # CLI entry point
├── internal/
│   ├── core/             # Core types and executor
│   ├── actions/          # Action implementations
│   ├── capabilities/     # Capability detection
│   └── logger/           # Logging system
├── go.mod
└── README.md

Building

# Build for current platform
go build -o ddc ./cmd/ddc

# Build for multiple platforms
GOOS=linux GOARCH=amd64 go build -o ddc-linux-amd64 ./cmd/ddc
GOOS=darwin GOARCH=amd64 go build -o ddc-darwin-amd64 ./cmd/ddc
GOOS=windows GOARCH=amd64 go build -o ddc-windows-amd64.exe ./cmd/ddc

Testing

go test ./...

Design Principles

  1. Explicit over Implicit: Every decision is visible in logs
  2. Simple over Clever: Readability over abstraction
  3. Complete over Perfect: Handle real-world failures
  4. Deterministic: Same inputs → Same outputs
  5. Auditable: Machine-readable results always

What DDC Is NOT

  • ❌ A CI/CD system
  • ❌ A workflow engine
  • ❌ A YAML/JSON DSL interpreter
  • ❌ An orchestrator
  • ❌ Magic automation

DDC is a tool. CI systems call DDC. DDC performs actions.

Roadmap

  • Core executor engine
  • publish-image action
  • deploy-compose action
  • transfer-artifacts action
  • validate-env action
  • Zig implementation
  • Windows support
  • Plugin system (maybe)

Contributing

Contributions are welcome! Please ensure:

  1. Code is explicit and readable
  2. No hidden behavior
  3. Comprehensive error handling
  4. Tests for new features
  5. Documentation updates

License

MIT License - See LICENSE file for details

Author

Dievilz

For questions, issues, or contributions, visit: https://github.com/dievilz/ddc

About

A deterministic, policy-driven CI/CD action executor

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published