Deploying Go Applications with DeployHQ: A Complete Guide

Devops & Infrastructure and Tutorials

Deploying Go Applications with DeployHQ: A Complete Guide

Go has become one of the most popular languages for building backend services, DevOps tools, and cloud-native applications. Its fast compilation, static binaries, and excellent concurrency support make it ideal for production deployments. In this guide, you'll learn how to deploy Go applications using DeployHQ, from simple web servers to complex microservices.

Why Go Excels at Production Deployments

Go's deployment story is remarkably simple compared to interpreted languages. When you build a Go application, you get a single static binary with no runtime dependencies. This means:

  • No need to install Go on your production server
  • No dependency conflicts between applications
  • Smaller deployment artifacts
  • Faster startup times

If you're coming from languages like Python or Node.js, you'll appreciate how much simpler Go deployments can be.

Project Structure for Deployable Go Applications

Before configuring DeployHQ, let's establish a production-ready project structure:

myapp/
├── cmd/
│   └── server/
│       └── main.go          # Application entry point
├── internal/
│   ├── handlers/            # HTTP handlers
│   ├── middleware/          # HTTP middleware
│   └── services/            # Business logic
├── pkg/                     # Public packages
├── go.mod                   # Go module definition
├── go.sum                   # Dependency checksums
├── Makefile                 # Build automation
└── .deployhq/
    └── deploy.sh            # Deployment script

This structure follows Go conventions and separates your application code from build artifacts. The cmd directory contains your application entry points, while internal holds private application code.

Setting Up Go Modules

Modern Go applications use Go modules for dependency management. Your go.mod file defines your module and dependencies:

module github.com/yourcompany/myapp

go 1.26

require (
    github.com/gin-gonic/gin v1.12.0
    github.com/joho/godotenv v1.5.1
)

DeployHQ's build pipeline handles downloading and caching these dependencies automatically during the build phase.

Configuring DeployHQ Build Commands

The key to deploying Go applications is configuring the right build commands. In your DeployHQ project settings, navigate to the Build Pipeline section and add these commands:

# Download dependencies
go mod download

# Run tests (optional but recommended)
go test ./...

# Build for Linux (most servers)
GOOS=linux GOARCH=amd64 go build -o ./bin/server ./cmd/server

The GOOS and GOARCH environment variables enable cross-compilation. This is crucial when your local machine runs macOS or Windows, but your server runs Linux.

flowchart LR
    A[Push Code] --> B[DeployHQ Detects Change]
    B --> C[Download Dependencies]
    C --> D[Run Tests]
    D --> E[Cross-Compile Binary]
    E --> F[Deploy to Server]
    F --> G[Restart Application]

Once your build commands are dialed in, DeployHQ runs them on every push — see automated build pipelines for how the build-then-deploy flow fits together.

Want to try this on a real project? Start a free DeployHQ trial, connect your repository, and paste in the build commands above.

Build Optimization Techniques

Go binaries can be further optimized for production:

# Stripped binary (smaller size)
go build -ldflags="-s -w" -o ./bin/server ./cmd/server

# With version information embedded
VERSION=$(git describe --tags --always)
go build -ldflags="-s -w -X main.Version=$VERSION" -o ./bin/server ./cmd/server

The -s flag strips the symbol table, and -w omits DWARF debugging information. Together, they noticeably reduce binary size — the trade-off is that stack traces from a stripped binary carry less debugging detail, so keep an unstripped build around if you rely on a debugger in production.

Environment Variables and Configuration

Go applications typically read configuration from environment variables. DeployHQ allows you to set these in the project settings:

package main

import (
    "os"
    "github.com/joho/godotenv"
)

func init() {
    // Load .env file in development
    godotenv.Load()
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    dbURL := os.Getenv("DATABASE_URL")
    // ... initialize application
}

For secure handling of sensitive variables like API keys and database credentials, see our guide on protecting your API keys.

Implementing Zero-Downtime Deployments

For production applications, you'll want zero-downtime deployments. For a Go service that takes two things: the new files have to appear atomically, and the process has to restart without dropping requests.

DeployHQ's atomic deployments handle the first part. Each release is uploaded to its own directory and the current symlink is only switched once the transfer completes, so your server never runs a half-uploaded binary.

The second part is yours to design. Restarting a single systemd service leaves a short window where nothing is listening. Go's fast startup keeps that window small, and the graceful shutdown handler later in this guide lets in-flight requests finish, but it is not zero. If you need none at all, run two instances behind Nginx or a load balancer and restart them one at a time.

Configure atomic deployments in DeployHQ and create this deployment script:

#!/bin/bash
# .deployhq/deploy.sh
set -euo pipefail

# `current` already points at the new release, so a restart
# picks up the new binary.
sudo systemctl restart myapp

# Wait for health check
for i in {1..30}; do
    if curl -s http://localhost:8080/health > /dev/null; then
        echo "Application started successfully"
        exit 0
    fi
    sleep 1
done

echo "Application failed to start"
exit 1

Add this script to run in the After deployment hooks in DeployHQ.

Systemd Service Configuration

On Linux servers, systemd manages your Go application's lifecycle. Create a service file:

# /etc/systemd/system/myapp.service
[Unit]
Description=My Go Application
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/myapp/current
ExecStart=/var/www/myapp/current/bin/server
Restart=always
RestartSec=5
Environment=PORT=8080
Environment=GIN_MODE=release
EnvironmentFile=/var/www/myapp/shared/.env

[Install]
WantedBy=multi-user.target

For more details on service management, check out our article on managing application services with systemd.

Health Checks and Graceful Shutdown

Production Go applications should implement health checks and graceful shutdown:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    router := setupRouter()

    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }

    // Start server in goroutine
    go func() {
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("Shutting down server...")

    // Give outstanding requests 30 seconds to complete
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited")
}

func setupRouter() http.Handler {
    mux := http.NewServeMux()

    // Health check endpoint
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })

    // Your application routes here

    return mux
}

Docker-Based Go Deployments

For containerized deployments, Go's static binaries enable minimal Docker images:

# Build stage
FROM golang:1.26-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server

# Runtime stage
FROM scratch

COPY --from=builder /server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080
ENTRYPOINT ["/server"]

The scratch base image contains nothing but your binary and the CA certificates you copy in, so the final image is a fraction of the size of one built on a full OS image. For Docker deployment strategies, see our guide on deploying to AWS ECS/EKS using DeployHQ shell servers.

flowchart TD
    subgraph "Multi-stage Docker Build"
        A[golang:1.26-alpine] --> B[Download Dependencies]
        B --> C[Compile Binary]
        C --> D[scratch image]
        D --> E[Copy Binary Only]
        E --> F["Final image: binary + CA certs"]
    end

Makefile for Build Automation

A Makefile standardizes build commands across development and CI/CD:

.PHONY: build test clean deploy

BINARY_NAME=server
VERSION=$(shell git describe --tags --always)
LDFLAGS=-ldflags "-s -w -X main.Version=$(VERSION)"

build:
    GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o bin/$(BINARY_NAME) ./cmd/server

build-local:
    go build $(LDFLAGS) -o bin/$(BINARY_NAME) ./cmd/server

test:
    go test -v -race ./...

test-coverage:
    go test -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out -o coverage.html

clean:
    rm -rf bin/
    rm -f coverage.out coverage.html

lint:
    golangci-lint run

# Run locally
run:
    go run ./cmd/server

In DeployHQ, your build command can simply be make build, keeping configuration DRY across environments.

Deployment Workflow Overview

Here's the complete deployment flow for a Go application:

sequenceDiagram
    participant Dev as Developer
    participant Git as GitHub/GitLab
    participant DHQ as DeployHQ
    participant Srv as Server

    Dev->>Git: git push
    Git->>DHQ: Webhook trigger
    DHQ->>DHQ: Pull latest code
    DHQ->>DHQ: go mod download
    DHQ->>DHQ: go test ./...
    DHQ->>DHQ: go build -o bin/server
    DHQ->>Srv: Transfer binary (atomic)
    DHQ->>Srv: Run deployment script
    Srv->>Srv: Restart service on new binary
    Srv->>Srv: Health check passes
    DHQ->>Dev: Deployment complete

Handling Database Migrations

Go applications often need database schema migrations. Tools like golang-migrate integrate well with DeployHQ:

# Build commands
go mod download
go build -o bin/server ./cmd/server
go build -o bin/migrate ./cmd/migrate

# After deployment hook
./bin/migrate -path ./migrations -database "$DATABASE_URL" up

For comprehensive migration strategies, see our guide on database migration strategies for zero-downtime deployments.

Monitoring and Observability

Add basic metrics to your Go application for deployment monitoring:

package main

import (
    "expvar"
    "net/http"
    "runtime"
    "time"
)

var (
    startTime = time.Now()
    requests  = expvar.NewInt("requests_total")
)

func init() {
    expvar.Publish("uptime", expvar.Func(func() interface{} {
        return time.Since(startTime).String()
    }))

    expvar.Publish("goroutines", expvar.Func(func() interface{} {
        return runtime.NumGoroutine()
    }))
}

func metricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requests.Add(1)
        next.ServeHTTP(w, r)
    })
}

Access metrics at /debug/vars endpoint (restrict in production).

Common Deployment Issues and Solutions

Binary Won't Execute

Ensure you're cross-compiling for the correct target:

# Check server architecture
uname -m  # x86_64 = amd64, aarch64 = arm64

# Build for ARM servers (AWS Graviton, etc.)
GOOS=linux GOARCH=arm64 go build -o bin/server ./cmd/server

Module Download Fails

Enable Go module proxy for reliable builds:

export GOPROXY=https://proxy.golang.org,direct
go mod download

Application Crashes on Startup

Check environment variables are properly set. Add startup logging:

func main() {
    log.Printf("Starting server version %s", Version)
    log.Printf("PORT: %s", os.Getenv("PORT"))
    log.Printf("DATABASE_URL: %s", maskConnectionString(os.Getenv("DATABASE_URL")))
    // ...
}

Best Practices Summary

  1. Use Go modules for dependency management
  2. Cross-compile for your target platform
  3. Strip binaries with -ldflags="-s -w" for smaller deployments
  4. Implement health checks for deployment verification
  5. Use graceful shutdown to avoid dropped connections
  6. Embed version info for deployment tracking
  7. Run tests in the build pipeline before deployment

Getting Started

Ready to deploy your Go application? Connect your Git repository to DeployHQ and configure the build pipeline as shown above. Every push to your main branch will then automatically build and deploy your Go application.

For teams managing multiple Go services, explore our multiple environments guide to coordinate staging and production deployments.


Have questions about deploying Go applications? Reach out to our team at support@deployhq.com or follow us on X (@deployhq) for the latest deployment tips.