Building Automation Services with Go: Practical Tools & Real-World Solutions
Backend Development

Building Automation Services with Go: Practical Tools & Real-World Solutions

Master building useful automation services and tools with Go. Learn to create production-ready services that solve real problems: log processors, API monitors, deployment tools, data pipelines, and more.

Por Omar Flores · Actualizado: February 17, 2026
#go #automation #services #cli-tools #monitoring #data-processing #deployment #real-world #practical #production #golang #systems #workflows #tools

Introduction: Go Services That Solve Real Problems

Most developers think of building automation as a side project. A script. Something quick and temporary.

Wrong.

The best automation is built as a service. A tool. Something that runs continuously, reliably, and solves a real problem.

Go is perfect for this. Not just because it’s fast and concurrent. But because it lets you build services that:

  • Deploy as a single binary
  • Run with minimal resources
  • Scale horizontally
  • Integrate with existing tools
  • Require zero infrastructure

This guide teaches you to build real automation services with Go. Services you’d actually use. Services you’d deploy to production.

Not theoretical examples. Not hello-world toys.

Real tools that solve real problems.


Chapter 1: The Anatomy of an Automation Service

Before we build, understand what makes a good automation service.

Essential Components

Component 1: Configuration Management

Your service needs to be configurable. Environment variables, config files, command-line flags.

type Config struct {
	APIKey      string
	Port        int
	DatabaseURL string
	LogLevel    string
}

func loadConfig() Config {
	return Config{
		APIKey:      os.Getenv("API_KEY"),
		Port:        getEnvInt("PORT", 8080),
		DatabaseURL: os.Getenv("DATABASE_URL"),
		LogLevel:    os.Getenv("LOG_LEVEL", "info"),
	}
}

Component 2: Structured Logging

Your service must be observable. You need logs, not print statements.

import "github.com/sirupsen/logrus"

func main() {
	log := logrus.New()
	log.SetFormatter(&logrus.JSONFormatter{})

	log.WithFields(logrus.Fields{
		"service": "api-monitor",
		"version": "1.0.0",
	}).Info("Service started")
}

Component 3: Health Checks

Services need to report their status. A /health endpoint.

func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
	health := map[string]string{
		"status":    "healthy",
		"timestamp": time.Now().Format(time.RFC3339),
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(health)
}

Component 4: Graceful Shutdown

Services should shut down cleanly. Not abruptly kill connections.

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)

go func() {
	<-sigChan
	log.Info("Shutdown signal received, closing gracefully...")
	server.Close()
}()

Component 5: Metrics & Monitoring

Services should expose metrics. For debugging, alerting, and observability.

import "github.com/prometheus/client_golang/prometheus"

var (
	requestsProcessed = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "requests_processed_total",
		Help: "Total number of requests processed",
	})

	processingTime = prometheus.NewHistogram(prometheus.HistogramOpts{
		Name: "request_duration_seconds",
		Help: "Time spent processing request",
	})
)

Chapter 2: Service 1 - API Health Monitor

Monitor multiple APIs and alert on failures.

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
)

type APICheck struct {
	Name     string
	URL      string
	Interval time.Duration
	Timeout  time.Duration
}

type Monitor struct {
	checks map[string]*APICheck
	logger *logrus.Logger
	mu     sync.RWMutex
}

func NewMonitor() *Monitor {
	logger := logrus.New()
	logger.SetFormatter(&logrus.JSONFormatter{})
	return &Monitor{
		checks: make(map[string]*APICheck),
		logger: logger,
	}
}

func (m *Monitor) AddCheck(check APICheck) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.checks[check.Name] = &check
}

func (m *Monitor) Start() {
	m.mu.RLock()
	checks := make([]*APICheck, 0, len(m.checks))
	for _, check := range m.checks {
		checks = append(checks, check)
	}
	m.mu.RUnlock()

	for _, check := range checks {
		go m.monitorAPI(check)
	}
}

func (m *Monitor) monitorAPI(check *APICheck) {
	ticker := time.NewTicker(check.Interval)
	defer ticker.Stop()

	for range ticker.C {
		m.performCheck(check)
	}
}

func (m *Monitor) performCheck(check *APICheck) {
	start := time.Now()
	client := &http.Client{
		Timeout: check.Timeout,
	}

	resp, err := client.Get(check.URL)
	duration := time.Since(start)

	if err != nil {
		m.logger.WithFields(logrus.Fields{
			"api":      check.Name,
			"status":   "down",
			"duration": duration,
			"error":    err.Error(),
		}).Error("API check failed")

		m.alertSlack(fmt.Sprintf("🚨 %s is DOWN: %v", check.Name, err))
		return
	}
	defer resp.Body.Close()

	// Discard body (don't need to read it)
	io.ReadAll(resp.Body)

	if resp.StatusCode != http.StatusOK {
		m.logger.WithFields(logrus.Fields{
			"api":        check.Name,
			"status":    "unhealthy",
			"http_code": resp.StatusCode,
			"duration":  duration,
		}).Warn("API returned non-200 status")

		m.alertSlack(fmt.Sprintf("⚠️ %s returned %d", check.Name, resp.StatusCode))
		return
	}

	m.logger.WithFields(logrus.Fields{
		"api":      check.Name,
		"status":   "healthy",
		"duration": duration,
	}).Debug("API check passed")
}

func (m *Monitor) alertSlack(message string) {
	webhook := os.Getenv("SLACK_WEBHOOK")
	if webhook == "" {
		return
	}

	payload := fmt.Sprintf(`{"text": "%s"}`, message)
	http.Post(webhook, "application/json", io.NopCloser(nil))
}

func main() {
	monitor := NewMonitor()

	// Add checks
	monitor.AddCheck(APICheck{
		Name:     "API Server",
		URL:      "http://localhost:8080/health",
		Interval: 30 * time.Second,
		Timeout:  5 * time.Second,
	})

	monitor.AddCheck(APICheck{
		Name:     "Database API",
		URL:      "http://localhost:5432/health",
		Interval: 60 * time.Second,
		Timeout:  5 * time.Second,
	})

	monitor.Start()

	// Keep running
	select {}
}

Deploy as service:

# Build
go build -o api-monitor

# Run
SLACK_WEBHOOK="https://hooks.slack.com/..." ./api-monitor

# Or in Docker
docker build -t api-monitor .
docker run -e SLACK_WEBHOOK="..." api-monitor

Chapter 3: Service 2 - Log Processor & Analyzer

Process logs in real-time, extract metrics, detect patterns.

package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type LogProcessor struct {
	errorCount      int64
	warningCount    int64
	successCount    int64
	slowRequests    int64
	slowThreshold   int
	errorPatterns   map[string]int64
	mu              sync.RWMutex
	started         time.Time
}

func NewLogProcessor(slowThreshold int) *LogProcessor {
	return &LogProcessor{
		slowThreshold: slowThreshold,
		errorPatterns: make(map[string]int64),
		started:       time.Now(),
	}
}

func (lp *LogProcessor) ProcessStream(reader io.Reader) error {
	scanner := bufio.NewScanner(reader)

	for scanner.Scan() {
		line := scanner.Text()
		lp.processLine(line)
	}

	return scanner.Err()
}

func (lp *LogProcessor) processLine(line string) {
	// Example log format: [2026-02-17T10:30:45] ERROR Database connection failed

	if strings.Contains(line, "ERROR") {
		atomic.AddInt64(&lp.errorCount, 1)
		lp.recordErrorPattern(line)
	} else if strings.Contains(line, "WARN") {
		atomic.AddInt64(&lp.warningCount, 1)
	} else if strings.Contains(line, "SUCCESS") || strings.Contains(line, "INFO") {
		atomic.AddInt64(&lp.successCount, 1)
	}

	// Detect slow requests
	if match := regexp.MustCompile(`duration=(\d+)ms`).FindStringSubmatch(line); len(match) > 1 {
		var duration int
		fmt.Sscanf(match[1], "%d", &duration)
		if duration > lp.slowThreshold {
			atomic.AddInt64(&lp.slowRequests, 1)
		}
	}
}

func (lp *LogProcessor) recordErrorPattern(line string) {
	// Extract error message
	parts := strings.SplitN(line, "ERROR", 2)
	if len(parts) < 2 {
		return
	}

	pattern := strings.TrimSpace(parts[1])
	// Keep only first 50 chars to group similar errors
	if len(pattern) > 50 {
		pattern = pattern[:50]
	}

	lp.mu.Lock()
	lp.errorPatterns[pattern]++
	lp.mu.Unlock()
}

func (lp *LogProcessor) Report() {
	elapsed := time.Since(lp.started)

	fmt.Printf("\n=== Log Analysis Report ===\n")
	fmt.Printf("Time: %.0f seconds\n", elapsed.Seconds())
	fmt.Printf("Errors: %d\n", atomic.LoadInt64(&lp.errorCount))
	fmt.Printf("Warnings: %d\n", atomic.LoadInt64(&lp.warningCount))
	fmt.Printf("Successes: %d\n", atomic.LoadInt64(&lp.successCount))
	fmt.Printf("Slow Requests (>%dms): %d\n", lp.slowThreshold, atomic.LoadInt64(&lp.slowRequests))

	if atomic.LoadInt64(&lp.errorCount) > 0 {
		fmt.Printf("\nTop Error Patterns:\n")
		lp.mu.RLock()
		for pattern, count := range lp.errorPatterns {
			if count > 0 {
				fmt.Printf("  %d × %s\n", count, pattern)
			}
		}
		lp.mu.RUnlock()
	}

	errorRate := float64(atomic.LoadInt64(&lp.errorCount)) /
		float64(atomic.LoadInt64(&lp.errorCount) + atomic.LoadInt64(&lp.successCount)) * 100

	fmt.Printf("\nError Rate: %.2f%%\n", errorRate)
}

func main() {
	slowThreshold := flag.Int("threshold", 1000, "Slow request threshold in milliseconds")
	flag.Parse()

	processor := NewLogProcessor(*slowThreshold)

	// Process stdin or file
	var input io.Reader
	if len(flag.Args()) > 0 {
		file, err := os.Open(flag.Args()[0])
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()
		input = file
	} else {
		input = os.Stdin
	}

	if err := processor.ProcessStream(input); err != nil {
		log.Fatal(err)
	}

	processor.Report()
}

Use it:

# Build
go build -o log-analyzer

# Process file
./log-analyzer --threshold 500 app.log

# Process live logs
tail -f app.log | ./log-analyzer

# Process from stdin
cat production.log | ./log-analyzer --threshold 1000

Chapter 4: Service 3 - Scheduled Task Runner

Run tasks on a schedule, with retries and notifications.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/robfig/cron/v3"
)

type Task struct {
	Name     string
	Schedule string
	Func     func(ctx context.Context) error
	MaxRetry int
}

type TaskRunner struct {
	cron     *cron.Cron
	tasks    map[string]*Task
	mu       sync.RWMutex
	logger   func(string, ...interface{})
	onError  func(string, error)
	onSuccess func(string, time.Duration)
}

func NewTaskRunner() *TaskRunner {
	return &TaskRunner{
		cron:   cron.New(),
		tasks:  make(map[string]*Task),
		logger: log.Printf,
	}
}

func (tr *TaskRunner) AddTask(task Task) error {
	tr.mu.Lock()
	defer tr.mu.Unlock()

	// Schedule the task
	_, err := tr.cron.AddFunc(task.Schedule, func() {
		tr.executeTask(task)
	})
	if err != nil {
		return fmt.Errorf("failed to schedule task: %w", err)
	}

	tr.tasks[task.Name] = &task
	tr.logger("Task scheduled: %s (%s)", task.Name, task.Schedule)

	return nil
}

func (tr *TaskRunner) executeTask(task Task) {
	var lastErr error
	start := time.Now()

	for attempt := 1; attempt <= task.MaxRetry; attempt++ {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
		err := task.Func(ctx)
		cancel()

		if err == nil {
			duration := time.Since(start)
			tr.logger("✓ %s completed in %v", task.Name, duration)
			if tr.onSuccess != nil {
				tr.onSuccess(task.Name, duration)
			}
			return
		}

		lastErr = err
		if attempt < task.MaxRetry {
			tr.logger("⚠ %s attempt %d/%d failed: %v, retrying...",
				task.Name, attempt, task.MaxRetry, err)
			time.Sleep(time.Duration(attempt*5) * time.Second)
		}
	}

	tr.logger("✗ %s failed after %d attempts: %v", task.Name, task.MaxRetry, lastErr)
	if tr.onError != nil {
		tr.onError(task.Name, lastErr)
	}
}

func (tr *TaskRunner) Start() {
	tr.cron.Start()
	tr.logger("Task runner started with %d tasks", len(tr.tasks))
}

func (tr *TaskRunner) Stop() {
	tr.cron.Stop()
	tr.logger("Task runner stopped")
}

// Example tasks
func backupDatabase(ctx context.Context) error {
	log.Println("Backing up database...")
	// Your backup logic
	time.Sleep(2 * time.Second)
	return nil
}

func cleanupTempFiles(ctx context.Context) error {
	log.Println("Cleaning up temp files...")
	// Your cleanup logic
	return nil
}

func sendDailyReport(ctx context.Context) error {
	log.Println("Sending daily report...")
	// Your report logic
	return nil
}

func main() {
	runner := NewTaskRunner()

	// Add tasks
	runner.AddTask(Task{
		Name:     "Database Backup",
		Schedule: "0 2 * * *", // 2 AM daily
		Func:     backupDatabase,
		MaxRetry: 3,
	})

	runner.AddTask(Task{
		Name:     "Cleanup Temp Files",
		Schedule: "@daily",
		Func:     cleanupTempFiles,
		MaxRetry: 2,
	})

	runner.AddTask(Task{
		Name:     "Daily Report",
		Schedule: "0 9 * * 1-5", // 9 AM weekdays
		Func:     sendDailyReport,
		MaxRetry: 3,
	})

	// Handle errors
	runner.onError = func(name string, err error) {
		fmt.Printf("Task %s failed: %v\n", name, err)
		// Send alert to Slack, email, etc.
	}

	runner.Start()

	// Graceful shutdown
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	<-sigChan

	runner.Stop()
}

Chapter 5: Service 4 - Data Pipeline

Transform and process data from multiple sources.

package main

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sync"
	"time"
)

type Pipeline struct {
	sources      []Source
	transformers []Transformer
	sinks        []Sink
	logger       *log.Logger
}

type Source interface {
	Name() string
	Fetch(ctx context.Context) ([]map[string]interface{}, error)
}

type Transformer interface {
	Transform(data []map[string]interface{}) []map[string]interface{}
}

type Sink interface {
	Write(data []map[string]interface{}) error
}

// Example source: API
type APISource struct {
	name string
	url  string
}

func (s *APISource) Name() string { return s.name }

func (s *APISource) Fetch(ctx context.Context) ([]map[string]interface{}, error) {
	req, _ := http.NewRequestWithContext(ctx, "GET", s.url, nil)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var data []map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&data)
	return data, nil
}

// Example transformer: Filter
type FilterTransformer struct {
	field string
	value interface{}
}

func (t *FilterTransformer) Transform(data []map[string]interface{}) []map[string]interface{} {
	result := make([]map[string]interface{}, 0)
	for _, item := range data {
		if val, ok := item[t.field]; ok && val == t.value {
			result = append(result, item)
		}
	}
	return result
}

// Example sink: CSV file
type CSVSink struct {
	filename string
	mu       sync.Mutex
}

func (s *CSVSink) Write(data []map[string]interface{}) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	file, err := os.Create(s.filename)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// Write headers
	if len(data) > 0 {
		headers := make([]string, 0)
		for k := range data[0] {
			headers = append(headers, k)
		}
		writer.Write(headers)

		// Write rows
		for _, item := range data {
			row := make([]string, len(headers))
			for i, h := range headers {
				row[i] = fmt.Sprintf("%v", item[h])
			}
			writer.Write(row)
		}
	}

	return nil
}

func NewPipeline() *Pipeline {
	return &Pipeline{
		logger: log.New(os.Stdout, "[PIPELINE] ", log.LstdFlags),
	}
}

func (p *Pipeline) AddSource(s Source) {
	p.sources = append(p.sources, s)
}

func (p *Pipeline) AddTransformer(t Transformer) {
	p.transformers = append(p.transformers, t)
}

func (p *Pipeline) AddSink(s Sink) {
	p.sinks = append(p.sinks, s)
}

func (p *Pipeline) Run(ctx context.Context) error {
	p.logger.Println("Pipeline starting")
	start := time.Now()

	// Fetch from all sources
	var allData []map[string]interface{}
	for _, source := range p.sources {
		p.logger.Printf("Fetching from %s...", source.Name())
		data, err := source.Fetch(ctx)
		if err != nil {
			p.logger.Printf("Error fetching from %s: %v", source.Name(), err)
			continue
		}
		allData = append(allData, data...)
		p.logger.Printf("Fetched %d records from %s", len(data), source.Name())
	}

	// Apply transformations
	for _, transformer := range p.transformers {
		p.logger.Println("Applying transformation...")
		allData = transformer.Transform(allData)
		p.logger.Printf("After transformation: %d records", len(allData))
	}

	// Write to sinks
	for _, sink := range p.sinks {
		p.logger.Println("Writing to sink...")
		if err := sink.Write(allData); err != nil {
			p.logger.Printf("Error writing to sink: %v", err)
		}
	}

	p.logger.Printf("Pipeline completed in %v", time.Since(start))
	return nil
}

func main() {
	pipeline := NewPipeline()

	// Add source
	pipeline.AddSource(&APISource{
		name: "API",
		url:  "https://api.example.com/data",
	})

	// Add transformer
	pipeline.AddTransformer(&FilterTransformer{
		field: "status",
		value: "active",
	})

	// Add sink
	pipeline.AddSink(&CSVSink{
		filename: "output.csv",
	})

	// Run
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()

	if err := pipeline.Run(ctx); err != nil {
		log.Fatal(err)
	}
}

Chapter 6: Service 5 - Deployment Orchestrator

Deploy to multiple environments with status tracking.

package main

import (
	"context"
	"fmt"
	"os/exec"
	"sync"
	"time"
)

type Deployment struct {
	ID          string
	Service     string
	Environment string
	Version     string
	Status      string
	StartTime   time.Time
	EndTime     time.Time
	Output      string
}

type DeploymentOrchestrator struct {
	deployments map[string]*Deployment
	mu          sync.RWMutex
	logger      func(string, ...interface{})
}

func NewDeploymentOrchestrator() *DeploymentOrchestrator {
	return &DeploymentOrchestrator{
		deployments: make(map[string]*Deployment),
		logger:      fmt.Printf,
	}
}

func (do *DeploymentOrchestrator) Deploy(service, environment, version string) (*Deployment, error) {
	id := fmt.Sprintf("%s-%s-%d", service, environment, time.Now().Unix())

	deployment := &Deployment{
		ID:          id,
		Service:     service,
		Environment: environment,
		Version:     version,
		Status:      "pending",
		StartTime:   time.Now(),
	}

	do.mu.Lock()
	do.deployments[id] = deployment
	do.mu.Unlock()

	go do.executeDeployment(deployment)

	return deployment, nil
}

func (do *DeploymentOrchestrator) executeDeployment(d *Deployment) {
	d.Status = "running"
	do.logger("Starting deployment %s\n", d.ID)

	// Step 1: Build
	if err := do.buildService(d); err != nil {
		d.Status = "failed"
		d.Output = err.Error()
		d.EndTime = time.Now()
		do.logger("Build failed: %v\n", err)
		return
	}

	// Step 2: Test
	if err := do.testService(d); err != nil {
		d.Status = "failed"
		d.Output = err.Error()
		d.EndTime = time.Now()
		do.logger("Tests failed: %v\n", err)
		return
	}

	// Step 3: Deploy
	if err := do.deployToEnvironment(d); err != nil {
		d.Status = "failed"
		d.Output = err.Error()
		d.EndTime = time.Now()
		do.logger("Deployment failed: %v\n", err)
		return
	}

	// Step 4: Verify
	if err := do.verifyDeployment(d); err != nil {
		d.Status = "failed"
		d.Output = err.Error()
		d.EndTime = time.Now()
		do.logger("Verification failed: %v\n", err)
		return
	}

	d.Status = "success"
	d.EndTime = time.Now()
	do.logger("Deployment %s completed successfully\n", d.ID)
}

func (do *DeploymentOrchestrator) buildService(d *Deployment) error {
	do.logger("Building %s:%s\n", d.Service, d.Version)
	cmd := exec.Command("go", "build", "-o", d.Service)
	output, err := cmd.CombinedOutput()
	d.Output += string(output)
	return err
}

func (do *DeploymentOrchestrator) testService(d *Deployment) error {
	do.logger("Testing %s\n", d.Service)
	cmd := exec.Command("go", "test", "./...")
	output, err := cmd.CombinedOutput()
	d.Output += string(output)
	return err
}

func (do *DeploymentOrchestrator) deployToEnvironment(d *Deployment) error {
	do.logger("Deploying to %s\n", d.Environment)
	cmd := exec.Command("kubectl", "set", "image",
		fmt.Sprintf("deployment=%s", d.Service),
		fmt.Sprintf("%s=registry/%s:%s", d.Service, d.Service, d.Version),
		"-n", d.Environment,
	)
	output, err := cmd.CombinedOutput()
	d.Output += string(output)
	return err
}

func (do *DeploymentOrchestrator) verifyDeployment(d *Deployment) error {
	do.logger("Verifying deployment of %s\n", d.Service)

	for i := 0; i < 30; i++ {
		cmd := exec.Command("kubectl", "rollout", "status",
			fmt.Sprintf("deployment/%s", d.Service),
			"-n", d.Environment,
		)
		if err := cmd.Run(); err == nil {
			return nil
		}
		time.Sleep(2 * time.Second)
	}

	return fmt.Errorf("deployment verification timeout")
}

func (do *DeploymentOrchestrator) GetStatus(id string) *Deployment {
	do.mu.RLock()
	defer do.mu.RUnlock()
	return do.deployments[id]
}

func (do *DeploymentOrchestrator) GetAll() []*Deployment {
	do.mu.RLock()
	defer do.mu.RUnlock()

	deployments := make([]*Deployment, 0, len(do.deployments))
	for _, d := range do.deployments {
		deployments = append(deployments, d)
	}
	return deployments
}

func main() {
	orchestrator := NewDeploymentOrchestrator()

	// Deploy multiple services
	deploys := []struct {
		service     string
		environment string
		version     string
	}{
		{"api-service", "staging", "v1.2.3"},
		{"web-service", "staging", "v1.2.3"},
		{"worker-service", "staging", "v1.2.3"},
	}

	for _, d := range deploys {
		deployment, _ := orchestrator.Deploy(d.service, d.environment, d.version)
		fmt.Printf("Started deployment: %s\n", deployment.ID)
	}

	// Monitor progress
	for {
		time.Sleep(5 * time.Second)

		allDeployments := orchestrator.GetAll()
		pending := 0
		for _, d := range allDeployments {
			if d.Status == "pending" || d.Status == "running" {
				pending++
			}
		}

		if pending == 0 {
			fmt.Println("All deployments complete")
			break
		}

		fmt.Printf("Still running: %d deployments\n", pending)
	}

	// Print final status
	for _, d := range orchestrator.GetAll() {
		duration := d.EndTime.Sub(d.StartTime)
		fmt.Printf("%s: %s (%.0fs)\n", d.ID, d.Status, duration.Seconds())
	}
}

Chapter 7: Deploying Services

Make your services production-ready.

Docker

Dockerfile:

FROM golang:1.21-alpine AS builder

WORKDIR /build
COPY . .
RUN go build -o service .

FROM alpine:latest

RUN apk --no-cache add ca-certificates curl

COPY --from=builder /build/service /usr/local/bin/

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

EXPOSE 8080

ENTRYPOINT ["service"]

Build and run:

docker build -t my-service:v1 .
docker run -p 8080:8080 my-service:v1

Kubernetes

deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
        - name: my-service
          image: my-service:v1
          ports:
            - containerPort: 8080
          env:
            - name: LOG_LEVEL
              value: "info"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: database-url
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 30

Deploy:

kubectl apply -f deployment.yaml
kubectl get pods -l app=my-service
kubectl logs -f deployment/my-service

Chapter 8: Monitoring Services

Keep your services healthy and observable.

Prometheus Metrics

import "github.com/prometheus/client_golang/prometheus"

var (
	tasksDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "tasks_duration_seconds",
			Help: "Time taken to execute tasks",
		},
		[]string{"task_name", "status"},
	)

	tasksTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "tasks_total",
			Help: "Total tasks executed",
		},
		[]string{"task_name", "status"},
	)
)

func init() {
	prometheus.MustRegister(tasksDuration, tasksTotal)
}

Health Endpoint

func healthHandler(w http.ResponseWriter, r *http.Request) {
	health := map[string]interface{}{
		"status":    "healthy",
		"uptime":    time.Since(startTime).Seconds(),
		"timestamp": time.Now().RFC3339,
		"version":   "1.0.0",
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(health)
}

Appendix A: Common Patterns

Error Handling:

if err != nil {
	log.WithError(err).Error("Operation failed")
	return fmt.Errorf("failed operation: %w", err)
}

Timeouts:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

Graceful Shutdown:

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
<-sigChan
cleanup()

Concurrency:

var wg sync.WaitGroup
for _, item := range items {
	wg.Add(1)
	go func(i Item) {
		defer wg.Done()
		process(i)
	}(item)
}
wg.Wait()

Appendix B: Service Deployment Checklist

Before Deploying:

  • ✅ Unit tests pass
  • ✅ Integration tests pass
  • ✅ Docker image builds
  • ✅ Health endpoint responds
  • ✅ Metrics exposed
  • ✅ Logging configured
  • ✅ Configuration in environment variables
  • ✅ Error handling complete

After Deploying:

  • ✅ Service starts without errors
  • ✅ Health checks passing
  • ✅ Metrics available
  • ✅ Logs appearing
  • ✅ Can handle graceful shutdown
  • ✅ Alerts configured
  • ✅ Monitoring dashboard set up

Conclusion: Services That Matter

Go gives you the tools to build automation services that:

  • Deploy easily (single binary)
  • Scale reliably (concurrent by default)
  • Monitor clearly (metrics and logging)
  • Run safely (graceful shutdown)
  • Integrate cleanly (HTTP, gRPC, CLI)

These aren’t hobby projects. They’re production services that run your operations.

Start with one. Build it right. Deploy it safely.

Then build the next one.