Development

Architecture

Understand how Odysseus works under the hood.


Overview

Odysseus is a deployment orchestration tool that manages Docker containers via SSH. It consists of two Ruby gems:

  • odysseus-core: Core functionality (config parsing, Docker/Caddy clients, deployment logic)
  • odysseus-cli: Command-line interface

System architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Local Machine                             │
│  ┌─────────────┐    ┌──────────────┐    ┌───────────────────┐  │
│  │ deploy.yml  │───▶│ odysseus-cli │───▶│   odysseus-core   │  │
│  └─────────────┘    └──────────────┘    └─────────┬─────────┘  │
│                                                    │             │
│  ┌─────────────────┐                              │             │
│  │ secrets.yml.enc │──────────────────────────────┤             │
│  └─────────────────┘                              │             │
└───────────────────────────────────────────────────┼─────────────┘

                                              SSH   │

┌─────────────────────────────────────────────────────────────────┐
│                        Target Server                             │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                         Docker                            │  │
│  │  ┌────────────────┐  ┌────────────────┐  ┌────────────┐ │  │
│  │  │ myapp-web-v1.0 │  │ myapp-jobs-v1.0│  │  myapp-db  │ │  │
│  │  └────────────────┘  └────────────────┘  └────────────┘ │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    Caddy (Reverse Proxy)                  │  │
│  │                                                           │  │
│  │  myapp.example.com ──────────▶ myapp-web-v1.0:3000      │  │
│  │                                                           │  │
│  │  Automatic HTTPS via Let's Encrypt                       │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Core components

Config parser

Reads and validates deploy.yml:

lib/odysseus/config/parser.rb
lib/odysseus/validators/config.rb

Normalizes configuration into internal structures and validates required fields.

SSH client

Manages SSH connections to servers:

lib/odysseus/deployer/ssh.rb

Uses net-ssh for secure connections with key-based authentication.

Docker client

Wraps Docker CLI commands:

lib/odysseus/docker/client.rb

Handles:

  • Container lifecycle (run, stop, remove)
  • Image management (pull, push, save, load)
  • Health checks and status

Caddy client

Manages Caddy reverse proxy:

lib/odysseus/caddy/client.rb

Uses Caddy's admin API to:

  • Add/remove routes dynamically
  • Configure upstream servers
  • Manage SSL certificates

Builder

Builds Docker images:

lib/odysseus/builder/client.rb

Supports:

  • Local builds
  • Remote builds via SSH
  • Multi-architecture builds

Secrets manager

Handles encrypted secrets:

lib/odysseus/secrets/encrypted_file.rb
lib/odysseus/secrets/loader.rb

Uses AES-256-GCM for encryption/decryption.

Host providers

Resolves deployment targets:

lib/odysseus/host_providers/base.rb
lib/odysseus/host_providers/static.rb
lib/odysseus/host_providers/aws_asg.rb

Supports static hosts and AWS Auto Scaling Group discovery.

Orchestrator

Coordinates deployments:

lib/odysseus/orchestrator/web_deploy.rb
lib/odysseus/orchestrator/job_deploy.rb
lib/odysseus/orchestrator/accessory_deploy.rb

Implements the deployment sequence for each role type.


Deployment flow

1. Configuration loading

deploy.yml → Parser → Normalized Config

           Validator → Errors/Warnings

2. Host resolution

servers.web.hosts → Static Provider → Host List
servers.web.aws   → AWS Provider   → Host List

3. Secrets decryption

ODYSSEUS_MASTER_KEY + secrets.yml.enc → Decrypted Secrets

4. Per-host deployment

For each server in each role:

1. Connect via SSH
2. Pull/load Docker image
3. Start new container
4. Wait for health check
5. Update Caddy routing (web only)
6. Drain old container
7. Stop old container
8. Cleanup old containers

Key design decisions

SSH-based

All operations happen over SSH. No agents or daemons required on servers.

Benefits:

  • Simple setup (just SSH access)
  • No additional security surface
  • Works with any server

Caddy for routing

Caddy provides automatic HTTPS and dynamic configuration.

Benefits:

  • Automatic Let's Encrypt certificates
  • Dynamic upstream management via API
  • Zero-downtime routing updates

Container-per-deploy

Each deployment creates a new container with a unique name:

myapp-web-v1.0.0
myapp-web-v1.1.0
myapp-web-v1.2.0

Benefits:

  • Easy rollback
  • Clear version history
  • Parallel running during transition

Encrypted secrets

Secrets are encrypted at rest and decrypted only during deployment.

Benefits:

  • Safe to commit to version control
  • Single source of truth
  • Standard encryption (AES-256-GCM)

File structure

odysseus-core/
├── lib/odysseus/
│   ├── config/
│   │   └── parser.rb           # YAML parsing
│   ├── validators/
│   │   └── config.rb           # Configuration validation
│   ├── deployer/
│   │   ├── executor.rb         # Main deployment logic
│   │   └── ssh.rb              # SSH connection management
│   ├── docker/
│   │   └── client.rb           # Docker CLI wrapper
│   ├── caddy/
│   │   └── client.rb           # Caddy admin API
│   ├── builder/
│   │   └── client.rb           # Image building
│   ├── secrets/
│   │   ├── encrypted_file.rb   # Encryption/decryption
│   │   └── loader.rb           # Secret loading
│   ├── orchestrator/
│   │   ├── web_deploy.rb       # Web role deployment
│   │   ├── job_deploy.rb       # Job role deployment
│   │   └── accessory_deploy.rb # Accessory deployment
│   └── host_providers/
│       ├── base.rb             # Base class
│       ├── static.rb           # Static hosts
│       └── aws_asg.rb          # AWS ASG discovery

odysseus-cli/
├── lib/odysseus/cli/
│   └── cli.rb                  # CLI implementation
└── exe/
    └── odysseus                # Executable entry point

Extension points

Custom host providers

Implement the base provider interface:

module Odysseus
  module HostProviders
    class Custom < Base
      def resolve
        # Return array of hostnames/IPs
      end
    end
  end
end

Custom validators

Add validation rules:

module Odysseus
  module Validators
    class Custom
      def validate(config)
        # Return array of error messages
      end
    end
  end
end
Previous
app & utilities