Skip to main content

Go 1.23.12 (CentOS Stream 9) AMI Administrator Guide

1. Quick Start Information

Connection Methods:

  • Access the instance via SSH using the ec2-user user. Use sudo to run commands requiring root privileges. To switch to the root user, use sudo su - root.

Install Information:

  • OS: CentOS Stream 9
  • Go version: 1.23.12
  • Go Install Directory (GOROOT): /usr/local/go
  • Go Workspace (GOPATH): $HOME/go
  • Environment Configuration: /etc/profile.d/go.sh

Quick Verification Commands:

  • Check Go version: go version
  • Check Go location: which go
  • View all environment variables: go env
  • Check installed tools: dlv version, gopls version, air -v

Development Tools Included:

  • Go Compiler: go
  • Go Formatter: gofmt
  • Delve Debugger: dlv (v1.26.0)
  • Language Server: gopls (v0.21.0)
  • Hot Reload Tool: air (v1.64.5)

Firewall Configuration:

  • Please allow SSH port 22.
  • For security, it is recommended to limit SSH access to trusted IPs only.

2. Overview

Welcome to this Go 1.23.12 AMI. This image is based on CentOS Stream 9 and provides a complete, production-ready development environment for building high-performance applications in Go.

This guide explains how to use this AMI and details its internal configuration.

What is Go?

Go (also known as Golang) is a statically typed, compiled programming language designed at Google. It is known for its simplicity, efficiency, and excellent support for concurrent programming. Go is widely used for building web servers, cloud services, DevOps tools, and distributed systems.

Core Features of This AMI:

  • Go 1.23.12: Latest stable release with official binary distribution
  • Delve Debugger: v1.26.0 for interactive debugging and troubleshooting
  • gopls Language Server: v0.21.0 for IDE integration (VS Code, Vim, etc.)
  • Air Hot Reload: v1.64.5 for automatic rebuild on file changes
  • Global PATH Configuration: All tools accessible system-wide via /etc/profile.d/go.sh
  • Sudo-Compatible: Symbolic links in /usr/bin/ for root access

Target Use Cases:

  • Building RESTful APIs and microservices
  • Developing CLI tools and system utilities
  • Creating cloud-native applications (Kubernetes, Docker)
  • Building concurrent and parallel systems
  • Prototyping network services and distributed systems

3. First Launch & Verification

Step 1: Connect to Your Instance

  1. Launch your instance in your cloud provider's console (e.g., AWS EC2)
  2. Ensure SSH port 22 is allowed in your security group
  3. Connect via SSH:
    ssh -i your-key.pem ec2-user@YOUR_PUBLIC_IP

Step 2: Verify Go Installation

Check the Go version:

go version

Expected Output:

go version go1.23.12 linux/amd64

Verify the installation path:

which go

Expected Output:

/usr/local/go/bin/go

View all Go environment variables:

go env

Key Variables:

GOROOT="/usr/local/go"
GOPATH="/home/ec2-user/go"
GOCACHE="/home/ec2-user/.cache/go-build"

Step 3: Verify Development Tools

Check Delve debugger:

dlv version

Expected Output:

Delve Debugger
Version: 1.26.0
Build: $Id: 7fd7302eab8b16d715a94af1b5dfbffc2e1359bc $

Check gopls language server:

gopls version

Expected Output:

golang.org/x/tools/gopls v0.21.0

Check Air hot reload tool:

air -v

Expected Output:

  __    _   ___
/ /\ | | | |_)
/_/--\ |_| |_| \_ v1.64.5, built with Go go1.25.7

Step 4: Create and Run a Test Program

Create a simple "Hello World" program:

mkdir -p ~/hello-go
cd ~/hello-go

Create main.go:

cat > main.go << 'EOF'
package main

import "fmt"

func main() {
fmt.Println("Hello from Go 1.23.12!")
}
EOF

Run the program directly:

go run main.go

Expected Output:

Hello from Go 1.23.12!

Build an executable:

go build -o hello
./hello

Expected Output:

Hello from Go 1.23.12!

Clean up:

cd ~
rm -rf ~/hello-go

4. Architecture & Detailed Configuration

4.1. Component Locations

ComponentInstallation PathPurpose
Go Root (GOROOT)/usr/local/go/Go compiler and standard library
Go Binaries/usr/local/go/bin/go, gofmt, godoc
Go Workspace (GOPATH)$HOME/go/User projects and dependencies
Development Tools/usr/local/bin/dlv, gopls, air
System Symlinks/usr/bin/go, gofmt (for sudo access)
Environment Config/etc/profile.d/go.shPATH and GOPATH configuration

4.2. Environment Configuration File

File: /etc/profile.d/go.sh

Complete Contents:

export PATH=$PATH:/usr/local/go/bin

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

How This Works:

  1. Line 1: Adds /usr/local/go/bin to PATH (for go, gofmt commands)
  2. Line 3: Sets GOPATH to $HOME/go (user workspace for projects)
  3. Line 4: Adds $GOPATH/bin to PATH (for user-installed Go tools)

This file is automatically sourced when users log in via SSH. Changes take effect immediately for new sessions.

Why /etc/profile.d/:

  • System-wide configuration (applies to all users)
  • Survives system updates
  • Standard location for custom environment scripts

All core executables are linked to /usr/bin/ for global access and sudo compatibility:

/usr/bin/go -> /usr/local/go/bin/go
/usr/bin/gofmt -> /usr/local/go/bin/gofmt

Why Symbolic Links:

  • Allows sudo go to work (sudo resets PATH)
  • Provides consistent command location across the system
  • Standard Unix pattern for third-party binaries

4.4. GOPATH Directory Structure

After creating your first project, the GOPATH directory will have this structure:

$HOME/go/
├── bin/ # Compiled executables from 'go install'
├── pkg/ # Compiled package objects (cache)
└── src/ # Source code (deprecated in Go 1.11+, use modules instead)

Modern Go Development (Go Modules):

Since Go 1.11, projects no longer need to be inside GOPATH/src. You can create projects anywhere:

mkdir -p ~/projects/myapp
cd ~/projects/myapp
go mod init github.com/yourusername/myapp

4.5. Installation Method

This AMI uses the official Go binary distribution from https://go.dev/dl/:

  • Source: go1.23.12.linux-amd64.tar.gz
  • Why Official Binary: Google-compiled, thoroughly tested, zero compilation time
  • Architecture: amd64 (x86_64) for maximum compatibility

No Compilation Required:

Unlike Erlang or other languages, Go is distributed as a pre-compiled binary. This means:

  • Instant installation (no 15-minute build process)
  • Guaranteed compatibility with official tooling
  • Consistent behavior across all systems

5. How-To-Create: Building This AMI from Scratch

This section explains exactly how this AMI was created, allowing you to:

  • Understand the configuration
  • Reproduce the setup on other systems
  • Customize the installation

Step 1: System Preparation

Update the system and install prerequisites:

# Update all packages
sudo dnf update -y

# Install wget and tar (minimal images may lack these)
sudo dnf install -y wget tar

Why These Packages:

  • wget: Required to download the Go binary archive
  • tar: Required to extract the .tar.gz file

Step 2: Remove Conflicting Installations

Remove any existing Go installation from package manager:

sudo dnf remove -y golang

Why This Step:

CentOS repositories may contain outdated Go versions (e.g., Go 1.20). Removing them prevents:

  • Binary conflicts (/usr/bin/go pointing to old version)
  • Environment variable conflicts
  • Version confusion

Step 3: Download and Install Go 1.23.12

Navigate to temporary directory:

cd /tmp

Download the official Go 1.23.12 binary:

wget https://go.dev/dl/go1.23.12.linux-amd64.tar.gz

Download Details:

Extract to /usr/local:

sudo tar -C /usr/local -xzf go1.23.12.linux-amd64.tar.gz

Extraction Details:

  • -C /usr/local: Change to /usr/local before extracting
  • -x: Extract files
  • -z: Decompress gzip
  • -f: Use file (not stdin)

Result: Creates /usr/local/go/ with the complete Go installation.

Installation Directory Structure:

/usr/local/go/
├── bin/ # Executables (go, gofmt, etc.)
├── src/ # Standard library source code
├── pkg/ # Compiled standard library
├── misc/ # Support files
└── VERSION # Version identifier

Step 4: Configure Environment Variables

Create the system-wide environment configuration file:

sudo tee /etc/profile.d/go.sh > /dev/null << 'EOF'
export PATH=$PATH:/usr/local/go/bin

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
EOF

File Breakdown:

export PATH=$PATH:/usr/local/go/bin

Adds Go binaries to PATH (go, gofmt, godoc)

export GOPATH=$HOME/go

Sets user workspace (where go install puts binaries)

export PATH=$PATH:$GOPATH/bin

Adds user-installed tools to PATH (dlv, gopls, etc.)

Activation:

For the current session:

source /etc/profile.d/go.sh

For all future sessions: Automatic (sourced at login)

Link Go commands to /usr/bin/ for sudo access:

sudo ln -sf /usr/local/go/bin/go /usr/bin/go
sudo ln -sf /usr/local/go/bin/gofmt /usr/bin/gofmt

Why This Is Important:

When you run sudo go, the system looks in /usr/bin/ (sudo resets PATH for security). Without symlinks:

go version        # Works (user PATH includes /usr/local/go/bin)
sudo go version # Fails (sudo PATH doesn't include /usr/local/go/bin)

With symlinks:

go version        # Works
sudo go version # Works

Step 6: Verify Base Installation

Check version:

go version

Expected Output:

go version go1.23.12 linux/amd64

Check location:

which go

Expected Output:

/usr/local/go/bin/go

Step 7: Install Delve Debugger

Install Delve to /usr/local/bin/:

sudo GOBIN=/usr/local/bin /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@latest

Command Breakdown:

  • sudo: Run as root to write to /usr/local/bin/
  • GOBIN=/usr/local/bin: Install binary to this directory (not $GOPATH/bin)
  • /usr/local/go/bin/go: Use absolute path (sudo doesn't have Go in PATH yet)
  • install: Compile and install the package
  • github.com/go-delve/delve/cmd/dlv: Package import path
  • @latest: Use the latest version

What is Delve:

Delve is a debugger specifically designed for Go. Features:

  • Set breakpoints and inspect variables
  • Step through code line-by-line
  • Examine goroutines and stack traces
  • Attach to running processes

Why /usr/local/bin/:

  • Global availability (in default PATH)
  • Survives GOPATH changes
  • Accessible via sudo

Verify installation:

dlv version

Expected Output:

Delve Debugger
Version: 1.26.0
Build: $Id: 7fd7302eab8b16d715a94af1b5dfbffc2e1359bc $

Step 8: Install gopls Language Server

Install gopls to /usr/local/bin/:

sudo GOBIN=/usr/local/bin /usr/local/go/bin/go install golang.org/x/tools/gopls@latest

What is gopls:

gopls (pronounced "go please") is the official Go language server implementing the Language Server Protocol (LSP). It provides:

  • Code completion and IntelliSense
  • Go to definition / Find references
  • Real-time error checking
  • Code formatting and imports organization

IDE Integration:

  • VS Code: Install "Go" extension (gopls auto-detected)
  • Vim/Neovim: Use vim-go or coc-go
  • Emacs: Use lsp-mode with gopls
  • Sublime Text: Use LSP-gopls

Verify installation:

gopls version

Expected Output:

golang.org/x/tools/gopls v0.21.0

Step 9: Install Air Hot Reload Tool

Install Air to /usr/local/bin/:

sudo GOBIN=/usr/local/bin /usr/local/go/bin/go install github.com/air-verse/air@latest

What is Air:

Air is a live reload utility for Go development. It watches your files and automatically rebuilds and restarts your application when changes are detected.

Features:

  • Configurable file watch patterns
  • Build error notifications
  • Customizable build and run commands
  • Color-coded logging

Typical Usage:

cd ~/myproject
air

Air will watch for changes and rebuild automatically.

Verify installation:

air -v

Expected Output:

  __    _   ___
/ /\ | | | |_)
/_/--\ |_| |_| \_ v1.64.5, built with Go go1.25.7

Remove the downloaded archive:

cd ~
rm -f /tmp/go1.23.12.linux-amd64.tar.gz

Space Saved: ~68 MB

This cleanup is optional for this AMI because the download is small. However, it's good practice before creating AMI snapshots.


6. Using the Development Environment

6.1. Creating a New Go Project with Modules

Create a project directory:

mkdir -p ~/projects/myapp
cd ~/projects/myapp

Initialize a Go module:

go mod init github.com/yourusername/myapp

This creates go.mod:

module github.com/yourusername/myapp

go 1.23

Create main.go:

package main

import "fmt"

func main() {
fmt.Println("Welcome to my app!")
}

Run the program:

go run .

6.2. Adding Dependencies

Add a dependency (example: Gin web framework):

go get github.com/gin-gonic/gin

This:

  1. Downloads the package
  2. Updates go.mod with the dependency
  3. Creates/updates go.sum with checksums

Use it in your code:

package main

import "github.com/gin-gonic/gin"

func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Hello World"})
})
r.Run(":8080")
}

Install all dependencies:

go mod download

Tidy up dependencies (remove unused):

go mod tidy

6.3. Building Executables

Build for current platform:

go build -o myapp

Build with optimizations (smaller binary):

go build -ldflags="-s -w" -o myapp

Flags Explained:

  • -s: Omit symbol table and debug info
  • -w: Omit DWARF symbol table

Cross-compile for different platforms:

# Linux ARM64
GOOS=linux GOARCH=arm64 go build -o myapp-arm64

# macOS Intel
GOOS=darwin GOARCH=amd64 go build -o myapp-macos

# Windows
GOOS=windows GOARCH=amd64 go build -o myapp.exe

6.4. Running Tests

Create a test file main_test.go:

package main

import "testing"

func TestExample(t *testing.T) {
if 1+1 != 2 {
t.Error("Math is broken!")
}
}

Run tests:

go test

Run with coverage:

go test -cover

Run with verbose output:

go test -v

Run specific test:

go test -run TestExample

Generate coverage report:

go test -coverprofile=coverage.out
go tool cover -html=coverage.out

6.5. Using Delve Debugger

Start debugger:

dlv debug

Common Delve commands:

(dlv) break main.main   # Set breakpoint
(dlv) continue # Start/continue execution
(dlv) next # Step to next line
(dlv) step # Step into function
(dlv) print myvar # Print variable value
(dlv) goroutines # List goroutines
(dlv) exit # Quit debugger

Debug a running process:

# Get process ID
ps aux | grep myapp

# Attach debugger
sudo dlv attach <PID>

6.6. Using Air for Hot Reload

Initialize Air in your project:

cd ~/projects/myapp
air init

This creates .air.toml configuration file.

Start development with hot reload:

air

What Air Does:

  1. Watches for file changes (*.go, *.html, etc.)
  2. Rebuilds the project automatically
  3. Restarts the application
  4. Shows build errors in real-time

Customize .air.toml:

[build]
cmd = "go build -o ./tmp/main ."
bin = "tmp/main"
include_ext = ["go", "tmpl", "html"]
exclude_dir = ["tmp", "vendor"]

6.7. Formatting and Linting

Format code:

gofmt -w .

Or use goimports (install first):

go install golang.org/x/tools/cmd/goimports@latest
goimports -w .

Difference:

  • gofmt: Formats code only
  • goimports: Formats code + manages imports (adds missing, removes unused)

Install golangci-lint (comprehensive linter):

curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin

Run linter:

golangci-lint run

7. Important File Locations

PathPurposeNotes
/usr/local/go/Go installationContains bin/, src/, pkg/
/usr/local/go/bin/goGo compilerMain executable
/usr/local/go/bin/gofmtCode formatterStandard formatting tool
/usr/bin/goGo symlinkFor sudo access
/usr/bin/gofmtgofmt symlinkFor sudo access
/usr/local/bin/dlvDelve debuggerInteractive debugger
/usr/local/bin/goplsLanguage serverIDE integration
/usr/local/bin/airHot reload toolDevelopment utility
/etc/profile.d/go.shEnvironment configSystem-wide PATH setup
$HOME/go/GOPATH workspaceUser projects and cache
$HOME/go/bin/User-installed toolsFrom go install

8. Troubleshooting

Problem: go: command not found

Cause: Environment not loaded or PATH not set.

Solution:

# Source the environment file
source /etc/profile.d/go.sh

# Or log out and back in
exit
ssh -i your-key.pem ec2-user@YOUR_IP

# Verify
go version

Problem: sudo go doesn't work

Cause: Symbolic links missing or sudo PATH doesn't include Go.

Solution:

# Verify symlinks exist
ls -la /usr/bin/go
ls -la /usr/bin/gofmt

# If missing, recreate them
sudo ln -sf /usr/local/go/bin/go /usr/bin/go
sudo ln -sf /usr/local/go/bin/gofmt /usr/bin/gofmt

# Test
sudo go version

Problem: go get fails with SSL errors

Cause: CA certificates outdated or missing.

Solution:

# Update CA certificates
sudo dnf update -y ca-certificates

# Retry
go get github.com/gin-gonic/gin

Problem: Module download fails with "connection refused"

Cause: Firewall blocking Go module proxy or network issue.

Solution:

# Check proxy setting
go env GOPROXY

# Try direct mode (bypass proxy)
GOPROXY=direct go get github.com/gin-gonic/gin

# Or use alternative proxy
GOPROXY=https://goproxy.io,direct go get github.com/gin-gonic/gin

Problem: dlv or gopls not found

Cause: Tools not installed or not in PATH.

Solution:

# Verify installation
which dlv
which gopls

# If missing, reinstall
sudo GOBIN=/usr/local/bin go install github.com/go-delve/delve/cmd/dlv@latest
sudo GOBIN=/usr/local/bin go install golang.org/x/tools/gopls@latest

# Verify
dlv version
gopls version

Problem: Build fails with "cannot find package"

Cause: Dependencies not downloaded or wrong import path.

Solution:

# Download all dependencies
go mod download

# Verify go.mod exists
ls -la go.mod

# If missing, initialize module
go mod init github.com/yourusername/myapp

# Clean module cache and retry
go clean -modcache
go mod download

Problem: Air doesn't rebuild on file changes

Cause: File watch limit exceeded or wrong configuration.

Solution:

# Increase inotify watch limit
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Check .air.toml configuration
cat .air.toml

# Verify include_ext includes your file types
# Default: ["go", "tmpl", "tpl", "html"]

9. Advanced Topics

9.1. Goroutines and Concurrency

Go's killer feature is easy concurrency:

package main

import (
"fmt"
"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
time.Sleep(time.Second)
results <- j * 2
}
}

func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)

// Start 3 workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}

// Send 9 jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)

// Collect results
for a := 1; a <= 9; a++ {
<-results
}
}

9.2. Building Web APIs with Gin

Install Gin framework:

go get github.com/gin-gonic/gin

Create a REST API:

package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

type User struct {
ID string `json:"id"`
Name string `json:"name"`
}

var users = []User{
{ID: "1", Name: "Alice"},
{ID: "2", Name: "Bob"},
}

func main() {
r := gin.Default()

r.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, users)
})

r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
for _, user := range users {
if user.ID == id {
c.JSON(http.StatusOK, user)
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
})

r.Run(":8080")
}

9.3. Performance Profiling

Add profiling to your app:

import _ "net/http/pprof"

Run your app and collect CPU profile:

go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30

Analyze memory usage:

go tool pprof http://localhost:8080/debug/pprof/heap

Generate flame graph:

go tool pprof -http=:9090 cpu.prof

9.4. Benchmarking

Create benchmark in _test.go:

func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
fibonacci(20)
}
}

Run benchmark:

go test -bench=.

With memory profiling:

go test -bench=. -benchmem

Compare benchmarks:

go test -bench=. > old.txt
# Make changes
go test -bench=. > new.txt
benchstat old.txt new.txt

9.5. Static Linking

Build fully static binary (no external dependencies):

CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o myapp

Verify it's static:

ldd myapp
# Output: "not a dynamic executable"

9.6. Using Docker with Go

Create Dockerfile:

FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -o myapp

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]

Build and run:

docker build -t myapp .
docker run -p 8080:8080 myapp

10. Security Considerations

10.1. Production Security Checklist

When deploying Go applications:

  1. Update Dependencies: Run go get -u ./... to update packages
  2. Scan for Vulnerabilities: Use govulncheck
    go install golang.org/x/vuln/cmd/govulncheck@latest
    govulncheck ./...
  3. Enable HTTPS: Never run production apps over HTTP
  4. Validate Input: Sanitize all user inputs
  5. Use Environment Variables: Never hardcode secrets
  6. Limit SSH Access: Use key-based authentication only
  7. Run as Non-Root: Create dedicated service user

10.2. Firewall Best Practices

Only expose necessary ports:

# SSH (restrict to your IP)
Source: YOUR_IP/32, Port: 22

# Web application (if public)
Source: 0.0.0.0/0, Port: 443 (HTTPS)

# Do NOT expose:
# - 8080 (Development server)
# - 6060 (pprof debug port)

10.3. Running as a System Service

Create a systemd service file /etc/systemd/system/myapp.service:

[Unit]
Description=My Go Application
After=network.target

[Service]
Type=simple
User=myapp
WorkingDirectory=/home/myapp
ExecStart=/home/myapp/myapp
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

11. Final Notes

What You've Got

This AMI provides a complete, production-ready Go development environment:

  • ✅ Go 1.23.12 official binary distribution
  • ✅ Delve v1.26.0 debugger for troubleshooting
  • ✅ gopls v0.21.0 language server for IDE integration
  • ✅ Air v1.64.5 hot reload for rapid development
  • ✅ Global PATH configuration for all users
  • ✅ Sudo-compatible installation
  1. Learn Go Basics:

  2. Build a Web Service:

  3. Explore Concurrency:

    • Goroutines and Channels tutorial
    • Concurrency patterns in Go
  4. Join the Community:

Performance Expectations

Go excels at:

  • Fast compilation (~100ms for small projects)
  • Low memory footprint (5-10MB for simple services)
  • High throughput (handling 10,000+ req/s on modest hardware)
  • Quick startup (milliseconds, not seconds)
  • Easy deployment (single static binary, no runtime dependencies)

Cost Optimization

For development:

  • t3.micro (1 vCPU, 1GB RAM): Sufficient for learning and small projects
  • t3.small (2 vCPU, 2GB RAM): Better for web services

For production:

  • t3.medium+ (2+ vCPU, 4GB+ RAM): Recommended starting point
  • c5.large+: For CPU-intensive workloads
  • t3.nano: Can run simple Go services (very cost-effective)

Go's efficient resource usage makes it one of the most cost-effective languages for cloud deployment.


Happy coding with Go! 🚀