Files
coolify-cli/ARCHITECTURE.md
T
2025-10-17 11:29:04 +00:00

17 KiB

Coolify CLI Architecture

This document describes the architecture and design principles of the Coolify CLI.

Overview

The Coolify CLI is a command-line interface for managing Coolify instances, servers, projects, and deployments. It follows a layered architecture pattern that separates concerns and promotes maintainability.

Architecture Layers

┌─────────────────────────────────────────────────────────┐
│                    User Interface                       │
│                   (Terminal/Shell)                      │
└────────────────────────┬────────────────────────────────┘
                         │
┌────────────────────────▼────────────────────────────────┐
│                  Command Layer (cmd/)                   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ servers  │  │  deploy  │  │ projects │  ...        │
│  └──────────┘  └──────────┘  └──────────┘             │
│  • CLI parsing & validation                             │
│  • Flag handling                                        │
│  • Output formatting                                    │
└────────────────────────┬────────────────────────────────┘
                         │
┌────────────────────────▼────────────────────────────────┐
│                 Service Layer (internal/service/)       │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ServerService│  │DeployService │  │ProjectService│  │
│  └─────────────┘  └──────────────┘  └──────────────┘  │
│  • Business logic                                       │
│  • Request validation                                   │
│  • Response transformation                              │
└────────────────────────┬────────────────────────────────┘
                         │
┌────────────────────────▼────────────────────────────────┐
│                API Client Layer (internal/api/)         │
│  ┌───────────────────────────────────────────────────┐ │
│  │              HTTP Client (api.Client)             │ │
│  └───────────────────────────────────────────────────┘ │
│  • HTTP requests/responses                              │
│  • Authentication (Bearer tokens)                       │
│  • Retry logic                                          │
│  • Error handling                                       │
└────────────────────────┬────────────────────────────────┘
                         │
┌────────────────────────▼────────────────────────────────┐
│                 Coolify API (External)                  │
│              https://instance.coolify.io/api/v1/        │
└─────────────────────────────────────────────────────────┘

Supporting Components

┌─────────────────────────────────────────────────────────┐
│         Configuration (internal/config/)                │
│  • Multi-instance management                            │
│  • Default instance selection                           │
│  • Token storage                                        │
│  • ~/.config/coolify/config.json                        │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│          Output Formatters (internal/output/)           │
│  ┌─────────┐  ┌────────┐  ┌─────────┐                 │
│  │  Table  │  │  JSON  │  │ Pretty  │                 │
│  └─────────┘  └────────┘  └─────────┘                 │
│  • Flexible output formats                              │
│  • Sensitive data masking                               │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│           Data Models (internal/models/)                │
│  • Server, Project, Resource, Deployment               │
│  • Request/Response structures                          │
│  • JSON marshaling/unmarshaling                         │
└─────────────────────────────────────────────────────────┘

Layer Responsibilities

1. Command Layer (cmd/)

Purpose: Handle CLI user interface and interaction

Responsibilities:

  • Parse command-line arguments and flags
  • Validate user input
  • Coordinate with service layer
  • Format and display output
  • Handle errors gracefully

Key Files:

  • root.go - Root command, global flags, initialization
  • servers.go - Server management commands
  • deploy.go - Deployment commands
  • context.go - Context (instance) configuration commands
  • projects.go - Project listing and inspection
  • etc.

Example:

var serversListCmd = &cobra.Command{
    Use:   "list",
    Short: "List all servers",
    RunE: func(cmd *cobra.Command, args []string) error {
        // Get API client
        client, err := getAPIClient(cmd)
        if err != nil {
            return err
        }

        // Use service layer
        service := service.NewServerService(client)
        servers, err := service.List(cmd.Context())
        if err != nil {
            return err
        }

        // Format and display output
        formatter, _ := getFormatter(cmd)
        return formatter.Format(servers)
    },
}

2. Service Layer (internal/service/)

Purpose: Implement business logic and coordinate API calls

Responsibilities:

  • Validate business rules
  • Coordinate multiple API calls if needed
  • Transform API responses to CLI-friendly format
  • Handle service-specific error cases

Key Files:

  • server.go - Server operations
  • deployment.go - Deployment operations
  • project.go - Project operations
  • resource.go - Resource operations
  • privatekey.go - SSH key operations
  • domain.go - Domain operations

Example:

type ServerService struct {
    client *api.Client
}

func (s *ServerService) List(ctx context.Context) ([]models.Server, error) {
    var servers []models.Server
    err := s.client.Get(ctx, "servers", &servers)
    return servers, err
}

3. API Client Layer (internal/api/)

Purpose: Handle all HTTP communication with Coolify API

Responsibilities:

  • Construct HTTP requests
  • Add authentication headers
  • Retry failed requests with exponential backoff
  • Parse HTTP responses
  • Convert HTTP errors to meaningful error messages

Key Files:

  • client.go - HTTP client implementation
  • error.go - API error handling
  • options.go - Client configuration options

Example:

type Client struct {
    baseURL    string
    token      string
    httpClient *http.Client
    retries    int
    timeout    time.Duration
}

func (c *Client) Get(ctx context.Context, path string, result interface{}) error {
    return c.doRequest(ctx, "GET", path, nil, result)
}

4. Configuration Layer (internal/config/)

Purpose: Manage CLI configuration and multiple instances

Responsibilities:

  • Load/save configuration from disk
  • Manage multiple Coolify instances
  • Select default instance
  • Store API tokens securely (file permissions)

Key Files:

  • config.go - Configuration structure and methods
  • instance.go - Instance definition
  • loader.go - File I/O operations

Configuration File (~/.config/coolify/config.json):

{
  "instances": [
    {
      "name": "prod",
      "fqdn": "https://coolify.example.com",
      "token": "your-api-token",
      "default": true
    },
    {
      "name": "staging",
      "fqdn": "https://staging.coolify.example.com",
      "token": "staging-token"
    }
  ]
}

5. Output Layer (internal/output/)

Purpose: Format data for display to users

Responsibilities:

  • Format data as tables, JSON, or pretty-printed JSON
  • Hide sensitive information unless --show-sensitive is used
  • Handle different data types (slices, structs, primitives)

Key Files:

  • formatter.go - Formatter interface
  • table.go - Table formatting
  • json.go - JSON formatting
  • pretty.go - Pretty JSON formatting

Supported Formats:

  • table - Default, human-readable tables
  • json - Compact JSON for scripting
  • pretty - Indented JSON for debugging

6. Models Layer (internal/models/)

Purpose: Define data structures

Responsibilities:

  • Define API request/response structures
  • JSON tags for marshaling
  • Common types and timestamps

Key Files:

  • server.go - Server-related types
  • project.go - Project-related types
  • resource.go - Resource types
  • deployment.go - Deployment types
  • common.go - Shared types

Data Flow

Example: Listing Servers

  1. User Input: coolify servers list --format=table

  2. Command Layer (cmd/servers.go):

    • Cobra parses the command
    • serversListCmd.RunE is executed
    • Gets API client using getAPIClient()
    • Creates ServerService instance
  3. Service Layer (internal/service/server.go):

    • ServerService.List() is called
    • Validates context (if needed)
    • Calls API client
  4. API Client Layer (internal/api/client.go):

    • Constructs GET request to /api/v1/servers
    • Adds Bearer token authentication
    • Sends HTTP request
    • Retries on failure (with backoff)
    • Parses JSON response
  5. Response Processing:

    • JSON unmarshaled to []models.Server
    • Returns to service layer
    • Returns to command layer
  6. Output Layer (internal/output/table.go):

    • Command layer creates table formatter
    • Formatter processes server data
    • Formats as table with columns
    • Writes to stdout
  7. User Output: Table displayed in terminal

Design Patterns

1. Dependency Injection

Services receive the API client as a constructor parameter:

func NewServerService(client *api.Client) *ServerService {
    return &ServerService{client: client}
}

Benefits:

  • Easy to test (can inject mock client)
  • Clear dependencies
  • Flexible configuration

2. Strategy Pattern (Output Formatters)

Different formatters implement the same interface:

type Formatter interface {
    Format(data interface{}) error
}

Benefits:

  • Easy to add new formats
  • Consistent API
  • Runtime format selection

3. Options Pattern (API Client)

Client configuration uses functional options:

client := api.NewClient(url, token,
    api.WithDebug(true),
    api.WithRetries(5),
    api.WithTimeout(60 * time.Second),
)

Benefits:

  • Optional parameters
  • Clear intent
  • Backward compatible

4. Error Wrapping

Errors are wrapped with context at each layer:

if err != nil {
    return fmt.Errorf("failed to list servers: %w", err)
}

Benefits:

  • Error context preserved
  • Stack trace maintained
  • Better debugging

Testing Strategy

Unit Tests

Each layer has comprehensive unit tests:

  • Commands: Mock services, test flag parsing
  • Services: Mock API client, test business logic
  • API Client: Use httptest.Server, test HTTP handling
  • Config: Test file I/O with temp directories
  • Output: Test formatting with buffers

Integration Tests

Test multiple layers together:

  • Commands + Services + Mock API
  • Config + File System
  • End-to-end workflows

Coverage Goals

  • Overall: 70%+
  • New features: 80%+
  • Critical paths: 90%+

Configuration Files

CLI Configuration

Location: ~/.config/coolify/config.json (Linux/macOS) Location: %APPDATA%\coolify\config.json (Windows)

Structure:

{
  "instances": [
    {
      "name": "prod",
      "fqdn": "https://coolify.example.com",
      "token": "your-token",
      "default": true
    }
  ],
  "lastUpdateCheckTime": "2025-01-15T10:30:00Z"
}

API Communication

Base URL

All API calls use: {fqdn}/api/v1/{endpoint}

Example: https://coolify.example.com/api/v1/servers

Authentication

Bearer token authentication:

Authorization: Bearer {token}

Request/Response

Content-Type: application/json

Request Body (POST):

{
  "name": "my-server",
  "ip": "192.168.1.100"
}

Response Body:

{
  "uuid": "abc123",
  "name": "my-server",
  "ip": "192.168.1.100"
}

Error Handling

HTTP errors are converted to CLI-friendly messages:

  • 401 → "Unauthenticated. Check your API token."
  • 404 → "Resource not found."
  • 500 → "Server error. Please try again."

Retry Logic

Failed requests are retried with exponential backoff:

  • Attempt 1: Immediate
  • Attempt 2: Wait 1s
  • Attempt 3: Wait 2s
  • Attempt 4: Wait 4s

Does not retry on 4xx errors (except 429 rate limit).

Security Considerations

API Token Storage

  • Stored in config file with restricted permissions (0600)
  • Never logged (even in debug mode)
  • Masked in output by default (use -s to show)

Sensitive Data Handling

  • Tokens masked as ******** in output
  • Use --show-sensitive flag to reveal
  • Debug logs sanitize sensitive data

HTTPS

  • All API communication uses HTTPS
  • Certificate validation enabled

Performance Optimizations

Concurrent Operations

Batch deployments run in parallel:

// Deploy multiple resources concurrently
var wg sync.WaitGroup
for _, name := range names {
    wg.Add(1)
    go func(n string) {
        defer wg.Done()
        deployResource(n)
    }(name)
}
wg.Wait()

Connection Reuse

HTTP client reuses connections:

c.httpClient = &http.Client{
    Transport: &http.Transport{
        MaxIdleConns:       10,
        IdleConnTimeout:    90 * time.Second,
    },
}

Minimal Dependencies

  • Use Go standard library when possible
  • Only essential external dependencies
  • Keep binary size small

Extensibility

Adding a New Command

  1. Create cmd/newfeature.go
  2. Define Cobra command
  3. Create service if needed (internal/service/newfeature.go)
  4. Add models if needed (internal/models/newfeature.go)
  5. Register command in init()
  6. Write tests

Adding a New Output Format

  1. Create internal/output/newformat.go
  2. Implement Formatter interface
  3. Add format constant
  4. Update NewFormatter() switch

Adding API Client Features

  1. Add method to internal/api/client.go
  2. Add tests in internal/api/client_test.go
  3. Use in service layer

Build & Release

Build Process

# Local build
go build -o coolify ./coolify

# Install locally
go install ./coolify

# Multi-platform release
goreleaser release --clean

Release Artifacts

  • Linux: amd64, arm64
  • macOS: amd64, arm64 (Apple Silicon)
  • Windows: amd64

Distribution

  • GitHub Releases
  • Install script: scripts/install.sh
  • Package managers (planned)

Future Enhancements

  • Shell completion improvements
  • Interactive mode
  • Configuration wizard
  • Plugin system
  • Telemetry (opt-in)
  • Cache layer for frequent queries

References