← Back to Cecep Azhar
Cecep Azhar #golang#cli#devops#automation#tools

Building CLI Tools in Go for DevOps Automation

Cecep Saeful Azhar • 16 Oct 2026
Building CLI Tools in Go for DevOps Automation
Go is the best language for building CLI tools. Here's how to build production-ready command-line tools for DevOps automation — with Cobra, real examples, and deployment tips.

Why Go for CLI Tools

Every time I need a CLI tool, I ask myself: "Could this be a Go binary?" The answer is almost always yes. Here's why:

I've built 20+ CLI tools in Go for DevOps tasks. Here's how I structure them.

Project Setup with Cobra

package main

import (
    "os"
    
    "github.com/spf13/cobra"
)

func main() {
    rootCmd := &cobra.Command{
        Use:   "mytool",
        Short: "DevOps automation toolkit",
        Long:  "A comprehensive toolkit for automating deployment, monitoring, and maintenance tasks.",
    }
    
    // Add subcommands
    rootCmd.AddCommand(deployCmd())
    rootCmd.AddCommand(statusCmd())
    rootCmd.AddCommand(backupCmd())
    rootCmd.AddCommand(migrateCmd())
    
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

Real Example: Database Backup Tool

Here's a production-ready backup tool I built for managing PostgreSQL backups across multiple servers:

var backupCmd = &cobra.Command{
    Use:   "backup",
    Short: "Backup database to local or cloud storage",
    RunE: func(cmd *cobra.Command, args []string) error {
        dbHost, _ := cmd.Flags().GetString("host")
        dbName, _ := cmd.Flags().GetString("database")
        storage, _ := cmd.Flags().GetString("storage")
        compress, _ := cmd.Flags().GetBool("compress")
        
        // Generate filename with timestamp
        filename := fmt.Sprintf("%s_%s.sql", dbName, time.Now().Format("2006-01-02_15-04-05"))
        
        if compress {
            filename += ".gz"
        }
        
        fmt.Printf("Starting backup: %s → %s\n", dbName, filename)
        
        // Execute pg_dump
        var cmd *exec.Cmd
        if compress {
            cmd = exec.Command("pg_dump", "-h", dbHost, "-d", dbName, "-Fc", "--no-owner")
        } else {
            cmd = exec.Command("pg_dump", "-h", dbHost, "-d", dbName, "-Fp", "--no-owner")
        }
        
        output, err := cmd.Output()
        if err != nil {
            return fmt.Errorf("pg_dump failed: %w", err)
        }
        
        // Upload to storage
        switch storage {
        case "s3":
            return uploadToS3(filename, output)
        case "gcs":
            return uploadToGCS(filename, output)
        default:
            return os.WriteFile(filename, output, 0644)
        }
    },
}

func init() {
    backupCmd.Flags().StringP("host", "H", "localhost", "Database host")
    backupCmd.Flags().StringP("database", "d", "", "Database name (required)")
    backupCmd.Flags().StringP("storage", "s", "local", "Storage backend: local, s3, gcs")
    backupCmd.Flags().BoolP("compress", "z", true, "Compress backup")
    backupCmd.MarkFlagRequired("database")
}

Real Example: Deployment Tool

A deployment tool that handles SSH, Docker, and service management:

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Deploy application to target environment",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        env := args[0]
        version, _ := cmd.Flags().GetString("version")
        
        // Load environment config
        config, err := loadEnvConfig(env)
        if err != nil {
            return fmt.Errorf("load config for %s: %w", env, err)
        }
        
        // Connect via SSH
        sshClient, err := ssh.Connect(config.Host, config.KeyPath)
        if err != nil {
            return fmt.Errorf("connect to %s: %w", config.Host, err)
        }
        defer sshClient.Close()
        
        // Execute deployment steps
        steps := []DeployStep{
            {Name: "Pull image", Cmd: fmt.Sprintf("docker pull %s:%s", config.Image, version)},
            {Name: "Stop old container", Cmd: fmt.Sprintf("docker stop %s", config.ContainerName)},
            {Name: "Start new container", Cmd: fmt.Sprintf("docker run -d --name %s %s:%s", config.ContainerName, config.Image, version)},
            {Name: "Health check", Cmd: "curl -f http://localhost:8080/health", Retries: 10, Delay: 2 * time.Second},
        }
        
        for _, step := range steps {
            fmt.Printf("[deploy] %s... ", step.Name)
            if err := executeWithRetry(sshClient, step); err != nil {
                fmt.Println("FAILED")
                return fmt.Errorf("step '%s': %w", step.Name, err)
            }
            fmt.Println("OK")
        }
        
        fmt.Printf("\nDeploy v%s to %s completed successfully!\n", version, env)
        return nil
    },
}

Cross-Compilation

Build for all platforms from one machine:

# Linux (most common for servers)
GOOS=linux GOARCH=amd64 go build -o mytool-linux-amd64 ./cmd/mytool

# macOS (ARM)
GOOS=darwin GOARCH=arm64 go build -o mytool-darwin-arm64 ./cmd/mytool

# Windows
GOOS=windows GOARCH=amd64 go build -o mytool.exe ./cmd/mytool

# All at once
for os in linux darwin windows; do
    for arch in amd64 arm64; do
        GOOS=$os GOARCH=$arch go build -o mytool-${os}-${arch} ./cmd/mytool
    done
done

Testing CLI Tools

Use cobra's testing utilities plus os/exec for integration tests:

func TestBackupCommand(t *testing.T) {
    // Set up test database
    db := setupTestDB(t)
    defer db.Close()
    
    // Execute command
    cmd := backupCmd()
    cmd.SetArgs([]string{"--host", "localhost", "--database", "testdb", "--storage", "local"})
    
    err := cmd.Execute()
    if err != nil {
        t.Fatalf("backup command failed: %v", err)
    }
    
    // Verify backup file exists
    files, _ := filepath.Glob("testdb_*.sql.gz")
    if len(files) == 0 {
        t.Fatal("no backup file created")
    }
    
    // Clean up
    for _, f := range files {
        os.Remove(f)
    }
}

Pro Tips

The Takeaway

Go CLI tools are the Swiss Army knife of DevOps. Fast to build, trivial to deploy, and incredibly reliable. If you're still writing shell scripts for complex automation, it's time to graduate to Go.

← More articles from Cecep Azhar Central Content Hub Home →