惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
Cisco Talos Blog
Cisco Talos Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
PCI Perspectives
PCI Perspectives
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
N
News and Events Feed by Topic
V
Vulnerabilities – Threatpost
T
Troy Hunt's Blog
GbyAI
GbyAI
C
CERT Recently Published Vulnerability Notes
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
量子位
Scott Helme
Scott Helme
月光博客
月光博客
Attack and Defense Labs
Attack and Defense Labs
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
Project Zero
Project Zero
G
GRAHAM CLULEY
博客园 - 【当耐特】
Recorded Future
Recorded Future
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
小众软件
小众软件
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
MongoDB | Blog
MongoDB | Blog
H
Hacker News: Front Page
Latest news
Latest news
P
Proofpoint News Feed

DigitalOcean Community Tutorials

Mastering grep with Regular Expressions for Efficient Text Search It's Time to Break Up with Your Cloud: Why AI Teams are Switching We Built a Private-Document AI App to Test Platform Security. Here Is What We Could Actually Verify. PostgreSQL Explained: A Complete Beginner-to-Advanced Guide How To Install and Configure Postfix on Ubuntu How To Build a Web Application Using Flask in Python 3 Build AI Reading List with DigitalOcean Functions and Mistral How To Concatenate Strings in Python How to Allow MySQL Remote Access Securely How To Install and Use Docker on Rocky Linux How To Build a Multi-Agent AI System with Docker Agent DSPy Use Cases: Build Optimized LLM Pipelines How To Submit AJAX Forms with jQuery Build an AI-Powered GPU Fleet Optimizer with the DigitalOcean AI Platform ADK Monitor GPU Utilization in Real Time: A Complete Guide Reduce File Size of Images in Linux - CLI and GUI methods Reduce PDF File Size in Linux: Tools and Methods How To Set Up a Private Docker Registry on Ubuntu How To Troubleshoot Terraform: Errors and Fixes How to Use Go Modules Python Multiprocessing Example: Process, Pool & Queue Convert Class Components to Functional Components with React Hooks How To Install and Configure Ansible on Ubuntu LLM Tokenizers Simplified: BPE, SentencePiece, and More How To Monitor System Authentication Logs on Ubuntu How to Use Traceroute and MTR to Diagnose Network Issues How to Deploy Postgres to Kubernetes Cluster Importing Packages in Go: A Complete Guide Create RAID Arrays with mdadm on Ubuntu How To Make an HTTP Server in Go How To Set Up Time Synchronization on Ubuntu How To Use Struct Tags in Go apt-key Deprecation: Add Repositories with GPG on Ubuntu Linux ps Command: 20 Real-World Examples Python struct.pack and struct.unpack for Binary Data Deadlock in Java: Examples, Detection, and Prevention How To Use Find and Locate to Search for Files on Linux Structured Resume Skill Extraction Using Mistral-7B Inference How to Use the Python Main Function How to Set Up NemoClaw on a DigitalOcean Droplet with 1-Click Build an End-to-End RAG Pipeline for LLM Applications From Single to Multi-Agent Systems: Key Infrastructure Needs Back Up Data to Object Storage Using Restic How to Generate Videos with LTX-2.3 on DigitalOcean GPU Droplets How To Install LAMP Stack (Apache, MySQL, PHP) on Ubuntu How to Download Files with cURL How To Use Variadic Functions in Go Generate UUIDs with uuidgen on Linux How To Use EJS to Template Your Node Application How to Install Node.js on Ubuntu (Step-by-Step Guide) MongoDB Indexes: Improve Query Performance with Node.js LLM Tool Calling with DigitalOcean AI Platform and Databases What are Text Diffusion Models? - An Overview Crafting a Game from Scratch with GPT-5.4 Building Long-Term Memory in AI Agents with LangGraph and Mem0 How To Install PHP 7.4 and Set Up a Local Development Environment on Ubuntu 20.04 Build a GraphQL API in Go to Upload Files to Spaces How To Lint and Format Code with ESLint in Visual Studio Code Train YOLO26 for Retail Object Detection on DigitalOcean GPUs How To Work with JSON in MySQL How to Use the JavaScript .map() Method Building a Scalable App with MongoDB Using DigitalOcean's MCP Server How to Create an SSH Key in Linux: Easy Step-by-Step Guide Measure MySQL Query Performance with mysqlslap How To Use *args and **kwargs in Python 3 Nemotron 3 helped me find the perfect dish rack? A2A vs MCP - How These AI Agent Protocols Actually Differ How To Install and Manage Supervisor Docker Container Images with Watchtower on Ubuntu Getting Started with Qwen3.5 Vision-Language Models How To Create a New Sudo-Enabled User on Ubuntu How to Use Ansible to Install and Set Up Docker on Ubuntu How To Enable Remote Desktop Protocol Using xrdp on Ubuntu 22.04 How To Convert a String to a List in Python How To Check If a String Contains Another String in Python How to Read a Properties File in Python Python Command Line Arguments: sys.argv, argparse, getopt Mastering Grep command in Linux/Unix: A Beginner's Tutorial Understanding Python Data Types How to Implement a Stack in C With Code Examples Python os.system() vs subprocess: Run System Commands How To Install and Use Docker Compose on Ubuntu How to Add and Delete Users on Ubuntu How To Order Query Results in Laravel Eloquent How To Define and Use Handlers in Ansible Playbooks How To Install and Use SQLite on Ubuntu How To Install and Use Homebrew on macOS How To Manage DateTime with Carbon in Laravel and PHP How To Install Git on Ubuntu How To Install and Secure Redis on Ubuntu How To Build and Install Go Programs on Linux How To Build a Node.js Application with Docker How To Add JavaScript to HTML How To Reset Your MySQL or MariaDB Root Password How To Add Images in Markdown How To Set Up a Production Elasticsearch Cluster with Ansible How To Set Up a Firewall Using firewalld on CentOS Understanding Systemd Units and Unit Files How To Set Up Replication in MySQL How To Use the .htaccess File
Using ldflags to Set Version Information for Go Applications
Anish Singh Walia · 2019-10-25 · via DigitalOcean Community Tutorials

Introduction

Using ldflags to set version information for Go applications is a standard practice for embedding build metadata into compiled binaries. The -ldflags flag in go build passes instructions to the Go linker (cmd/link), letting you set the value of string variables at compile time without modifying source code. This means you can inject a version number, Git commit hash, build timestamp, or any other string into your binary directly from the command line or a CI/CD pipeline.

When deploying applications into production, this version metadata improves monitoring, logging, and debugging by giving you exact build traceability. Without it, identifying which code is running on a given server requires manual tracking that is tedious and error-prone. The ldflags -X approach solves this by writing values into package-level string variables during the link phase, producing a self-describing binary with zero runtime cost.

In this tutorial, you will build a Go application that prints version, build time, and Git commit information. You will use -ldflags -X to inject those values at build time, target variables in sub-packages, add a --version CLI flag, automate the build with a Makefile and GitHub Actions, and compare the ldflags approach with debug.ReadBuildInfo (available since Go 1.18). By the end, you will have a reusable build pipeline pattern for embedding version info into any Go project.

Key Takeaways

  • The -ldflags "-X 'package.Variable=value'" syntax sets package-level string variables at link time without changing source code.
  • Only string type variables at the package level can be set with -X. Constants, non-string types, and function-initialized variables are not supported.
  • Use shell substitution like $(git rev-parse --short HEAD) and $(date -u +%Y-%m-%dT%H:%M:%SZ) to inject dynamic build metadata.
  • Go 1.18+ automatically embeds VCS metadata accessible through debug.ReadBuildInfo(), which works without any ldflags configuration.
  • A Makefile or GitHub Actions workflow can automate ldflags injection so every build gets consistent, accurate version information.
  • The ldflags approach works with all Go versions and gives you explicit control, while debug.ReadBuildInfo requires Go 1.18+ and depends on VCS state being available at build time.

Prerequisites

To follow the examples in this tutorial, you will need:

Understanding the -X Linker Flag

The -ldflags flag passes arguments to the Go linker during go build. The ld in ldflags stands for linker, and the flags control how the linker assembles the final binary. The most commonly used linker flag is -X, which writes a value into a string variable at link time.

The full syntax looks like this:

  1. go build -ldflags="-X 'package_path.VariableName=value'"

The double quotes wrap the entire ldflags string. Inside, -X takes a single argument in the format package_path.VariableName=value. The package path is the full Go import path to the package containing the variable. The variable must be a package-level string variable (not a constant, not a non-string type, and not initialized by a function call).

Variable Declaration Rules

For -X to work, the target variable must meet these conditions:

  • It must be a package-level variable (not declared inside a function).
  • It must be of type string.
  • It can be exported or unexported (both Version and version work).
  • It cannot be a const, because constants are inlined at compile time and have no runtime address for the linker to write to.
  • It cannot be initialized by a function call like var Version = getVersion().

Common Mistakes and How to Avoid Them

Wrong package path: If you pass an incorrect package path, the build succeeds silently but the variable keeps its default value. Always verify the import path in your go.mod file or use go tool nm to inspect the binary (shown later in this tutorial).

Using const instead of var: The linker cannot modify constants. Change const Version = "dev" to var Version = "dev".

Missing quotes around the value: If your value contains spaces (like a date string), wrap the entire -X argument in single quotes:

  1. go build -ldflags="-X 'main.BuildTime=2026-04-06 14:30:00'"

Step 1: Setting Up a Go Project for Version Injection

In this step, you will create a Go module with a version variable and build it with a static default value.

Create a new project directory and initialize a Go module:

  1. mkdir go-version-example
  1. cd go-version-example
  1. go mod init go-version-example

Create the entry point main.go:

main.go

package main

import (
 "fmt"
)

var Version = "development"

func main() {
 fmt.Println("Version:\t", Version)
}

The Version variable defaults to "development". This default value appears when you build without -ldflags, making it clear during local development that the binary was not built through a release process.

Build and run the application:

  1. go build -o app
  1. ./app

You will see:

Output

Version: development

Now build again with -ldflags to set the version:

  1. go build -o app -ldflags="-X 'main.Version=v1.0.0'"
  1. ./app

The output now shows the injected version:

Output

Version: v1.0.0

The main in main.Version is the package path. Since Version lives in the main package, the path is simply main. For variables in other packages, you use the full import path, which is covered in a later step.

For more on how go build works and its options, see How To Build and Install Go Programs.

Step 2: Injecting Git Commit Hash and Build Timestamp

A version string alone does not tell you the exact source code state behind a binary. Injecting the Git commit hash and build timestamp gives you full traceability.

Update main.go to include additional variables:

main.go

package main

import (
 "fmt"
)

var (
 Version   = "development"
 CommitSHA = "none"
 BuildTime = "unknown"
)

func main() {
 fmt.Println("Version:\t", Version)
 fmt.Println("Commit:\t\t", CommitSHA)
 fmt.Println("Built:\t\t", BuildTime)
}

Build with shell substitution to capture the Git hash and current UTC time:

  1. go build -o app -ldflags="-X 'main.Version=v1.0.0' -X 'main.CommitSHA=$(git rev-parse --short HEAD)' -X 'main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'"

git rev-parse --short HEAD returns the abbreviated commit hash (7 characters). date -u +%Y-%m-%dT%H:%M:%SZ produces a UTC timestamp in ISO 8601 format, which avoids spaces and time zone ambiguity.

Run the binary:

  1. ./app

You will see output similar to:

Output

Version: v1.0.0 Commit: a1b2c3d Built: 2026-04-06T14:30:00Z

The exact commit hash and timestamp will reflect your repository state and build time.

Displaying Version Info via a --version CLI Flag

Users and operators expect a --version flag on CLI tools. Go’s standard library flag package makes this straightforward.

Update main.go:

main.go

package main

import (
 "flag"
 "fmt"
 "os"
)

var (
 Version   = "development"
 CommitSHA = "none"
 BuildTime = "unknown"
)

func main() {
 showVersion := flag.Bool("version", false, "Print version information and exit")
 flag.Parse()

 if *showVersion {
  fmt.Printf("Version:   %s\nCommit:    %s\nBuilt:     %s\n", Version, CommitSHA, BuildTime)
  os.Exit(0)
 }

 fmt.Println("Application is running...")
}

Build with ldflags and test:

  1. go build -o app -ldflags="-X 'main.Version=v1.0.0' -X 'main.CommitSHA=$(git rev-parse --short HEAD)' -X 'main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
  1. ./app --version

Output

Version: v1.0.0 Commit: a1b2c3d Built: 2026-04-06T14:30:00Z

Running ./app without the flag starts the application normally.

Step 3: Using ldflags with Packages Outside of main

In real projects, version information often lives in a dedicated package rather than main, so it can be imported by multiple parts of the application (HTTP handlers, logging middleware, health checks). The -X flag requires the full import path to reach variables outside the main package.

Create a version sub-package:

  1. mkdir version

Create version/version.go:

version/version.go

package version

var (
 Version   = "development"
 CommitSHA = "none"
 BuildTime = "unknown"
)

Update main.go to import and use this package:

main.go

package main

import (
 "flag"
 "fmt"
 "os"

 "go-version-example/version"
)

func main() {
 showVersion := flag.Bool("version", false, "Print version information and exit")
 flag.Parse()

 if *showVersion {
  fmt.Printf("Version:   %s\nCommit:    %s\nBuilt:     %s\n",
   version.Version, version.CommitSHA, version.BuildTime)
  os.Exit(0)
 }

 fmt.Println("Application is running...")
}

The -X flag now needs the full import path go-version-example/version:

  1. go build -o app -ldflags="-X 'go-version-example/version.Version=v1.0.0' -X 'go-version-example/version.CommitSHA=$(git rev-parse --short HEAD)' -X 'go-version-example/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
  1. ./app --version

Output

Version: v1.0.0 Commit: a1b2c3d Built: 2026-04-06T14:30:00Z

Finding the Correct Package Path with go tool nm

If you are unsure of the exact import path for a variable, build the binary first and then inspect it:

  1. go build -o app
  1. go tool nm ./app | grep version

The output will include lines like:

Output

55d2c0 D go-version-example/version.BuildTime 55d2d0 D go-version-example/version.CommitSHA 55d2e0 D go-version-example/version.Version

The D indicates a data symbol. The full path before the . is what you pass to -X.

Note: go tool nm does not support package names containing non-ASCII characters, ", or % characters.

Step 4: Automating Version Injection with a Makefile

Typing the full ldflags string on every build is repetitive and error-prone. A Makefile captures the build command in a single reusable target.

Create a Makefile in the project root:

Makefile

VERSION ?= $(shell git describe --tags --always --dirty)
COMMIT  := $(shell git rev-parse --short HEAD)
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
PKG := go-version-example/version

LDFLAGS := -X '$(PKG).Version=$(VERSION)' \
           -X '$(PKG).CommitSHA=$(COMMIT)' \
           -X '$(PKG).BuildTime=$(BUILD_TIME)'

.PHONY: build clean

build:
 go build -o app -ldflags="$(LDFLAGS)"

clean:
 rm -f app

git describe --tags --always --dirty produces a human-readable version from the nearest Git tag. If the working tree has uncommitted changes, it appends -dirty. The ?= operator lets you override VERSION from the command line: make build VERSION=v2.0.0.

Run the build:

  1. make build
  1. ./app --version

Integrating into a GitHub Actions Workflow

For automated releases, add a GitHub Actions workflow that injects version information from the Git tag:

.github/workflows/release.yml

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.26'

      - name: Build binary
        run: |
          VERSION=${{ github.ref_name }}
          COMMIT=$(git rev-parse --short HEAD)
          BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
          PKG=go-version-example/version
          go build -o app -ldflags="-X '${PKG}.Version=${VERSION}' -X '${PKG}.CommitSHA=${COMMIT}' -X '${PKG}.BuildTime=${BUILD_TIME}'"

      - name: Verify version
        run: ./app --version

This workflow triggers when you push a Git tag that starts with v (for example, v1.2.0). The github.ref_name context variable contains the tag name, which becomes the version string. Each binary built this way carries the exact tag, commit, and build time from the CI environment.

Step 5: Alternative Approach with debug.ReadBuildInfo

Starting with Go 1.12, the runtime/debug package provides debug.ReadBuildInfo(), which returns metadata about the running binary without any ldflags setup. Go 1.18 expanded this to include VCS (Version Control System) information when the binary is built inside a Git repository.

Create a standalone example to see what debug.ReadBuildInfo provides:

buildinfo_example.go

package main

import (
 "fmt"
 "runtime/debug"
)

func main() {
 info, ok := debug.ReadBuildInfo()
 if !ok {
  fmt.Println("Build info not available.")
  return
 }

 fmt.Println("Go version:", info.GoVersion)
 fmt.Println("Module path:", info.Path)
 fmt.Println("Module version:", info.Main.Version)

 for _, setting := range info.Settings {
  switch setting.Key {
  case "vcs.revision":
   fmt.Println("VCS revision:", setting.Value)
  case "vcs.time":
   fmt.Println("VCS time:", setting.Value)
  case "vcs.modified":
   fmt.Println("VCS modified:", setting.Value)
  }
 }
}

Save this as buildinfo_example.go in your project directory (replacing main.go temporarily or renaming the file). Build and run it inside the Git repository. Use go build . (not go build buildinfo_example.go) so that Go includes VCS metadata:

  1. go build -o buildinfo .
  1. ./buildinfo

Output

Go version: go1.26 Module path: go-version-example Module version: (devel) VCS revision: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 VCS time: 2026-04-06T12:00:00Z VCS modified: false

The vcs.revision key contains the full commit hash, vcs.time is the commit timestamp in RFC 3339 format, and vcs.modified indicates whether the working tree had uncommitted changes.

When to Use debug.ReadBuildInfo vs ldflags

Use debug.ReadBuildInfo when:

  • You want automatic VCS metadata without configuring build scripts.
  • Your binary is always built from a clean Git repository.
  • You do not need a custom version string separate from the Git tag.

Use ldflags -X when:

  • You need to inject a specific release version (like v2.1.0) that differs from the Git state.
  • Your build environment does not have VCS access (for example, building from a source tarball).
  • You need to support Go versions before 1.18.
  • You want full control over exactly what metadata is embedded.

In many production setups, teams use both: ldflags for the release version string and debug.ReadBuildInfo for supplementary build metadata.

Comparison of Version Injection Methods

The following table compares three approaches for embedding version information in Go binaries:

Feature ldflags -X debug.ReadBuildInfo Config file (embedded or external)
Go version required All versions Go 1.18+ for VCS info All versions
Setup required Build script or Makefile None (automatic) File generation step
Custom version string Yes (any string) Limited to Git tag/pseudo-version Yes (any format)
Git commit hash Manual via shell substitution Automatic (vcs.revision) Manual generation
Build timestamp Manual via shell substitution Automatic (vcs.time) Manual generation
Works without VCS Yes No VCS fields without a repository Yes
Runtime overhead None Minimal (reads embedded data) File I/O if external
Binary size impact Negligible (static strings) Negligible (already embedded by Go) Adds file content
CI/CD compatibility Works everywhere Requires VCS state in build env Works everywhere

For most Go projects, ldflags -X is the most portable and widely supported approach. debug.ReadBuildInfo is a good supplement when you want automatic metadata without maintaining build scripts. Config files are useful when version info needs to be shared with non-Go components (for example, a frontend that reads a version.json file).

Practical Example: Exposing Version Info in an HTTP Server

In production, version information is often served from a /version or /healthz endpoint so monitoring tools and load balancers can verify which build is running.

Create server.go:

server.go

package main

import (
 "encoding/json"
 "flag"
 "fmt"
 "log"
 "net/http"
 "os"

 "go-version-example/version"
)

func main() {
 showVersion := flag.Bool("version", false, "Print version information and exit")
 flag.Parse()

 if *showVersion {
  fmt.Printf("Version:   %s\nCommit:    %s\nBuilt:     %s\n",
   version.Version, version.CommitSHA, version.BuildTime)
  os.Exit(0)
 }

 http.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  json.NewEncoder(w).Encode(map[string]string{
   "version":   version.Version,
   "commit":    version.CommitSHA,
   "buildTime": version.BuildTime,
  })
 })

 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello from go-version-example %s", version.Version)
 })

 log.Println("Starting server on :8080")
 log.Fatal(http.ListenAndServe(":8080", nil))
}

Build and run the server:

  1. go build -o server -ldflags="-X 'go-version-example/version.Version=v1.0.0' -X 'go-version-example/version.CommitSHA=$(git rev-parse --short HEAD)' -X 'go-version-example/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
  1. ./server

In another terminal, query the version endpoint:

  1. curl http://localhost:8080/version

Output

{"buildTime":"2026-04-06T14:30:00Z","commit":"a1b2c3d","version":"v1.0.0"}

This pattern is used by major Go projects including Kubernetes and Prometheus to expose build metadata at runtime.

FAQs

1. What types of variables can be set using -ldflags -X in Go?

Only package-level variables of type string can be set. The variable must be declared with var, not const. It can be exported or unexported, but it cannot have its value initialized by a function call like var Version = getVersion(). Non-string types (int, bool, struct) are not supported.

2. Can I use -ldflags -X to set variables in packages other than main?

Yes. Use the full import path of the package in the -X argument. For example, if your module is github.com/user/repo and the variable is in the version package, use: -X 'github.com/user/repo/version.Version=1.0.0'.

3. What happens if the variable name in -ldflags -X does not match any variable in the binary?

The build succeeds without any error or warning, but the variable retains its default value. This is a silent failure. During development, always print the injected variables at startup to verify they were set correctly.

4. How do I inject the current Git commit hash into a Go binary at build time?

Use shell command substitution in the build command: go build -ldflags "-X main.CommitSHA=$(git rev-parse --short HEAD)". This captures the abbreviated 7-character commit hash and embeds it in the binary.

5. What is the difference between -ldflags -X and debug.ReadBuildInfo?

-ldflags -X requires you to pass values explicitly at build time, giving you full control over what is embedded. debug.ReadBuildInfo() reads metadata automatically from the Go module system and VCS state (Go 1.18+). The ldflags approach works with all Go versions and does not depend on a VCS repository being present. debug.ReadBuildInfo provides automatic VCS metadata but only works when the binary is built inside a Git repository with module mode enabled.

6. Does using -ldflags affect binary size or runtime performance?

The impact on binary size is negligible. The injected strings are stored as static data in the binary, adding only the number of bytes in the string values themselves. There is no measurable runtime performance impact because the values are read as regular Go variables.

7. Can I use -ldflags -X with go install?

Yes. The same syntax works: go install -ldflags "-X main.Version=1.2.0" ./.... The resulting binary installed to $GOPATH/bin will contain the injected values.

8. How do I use -ldflags in a GitHub Actions workflow for automated releases?

Define the version from the Git tag using ${{ github.ref_name }} and pass it to go build. A complete example is provided in the Integrating into a GitHub Actions Workflow section of this tutorial.

Conclusion

In this tutorial, you used -ldflags -X to inject version information, Git commit hashes, and build timestamps into Go binaries at build time. You built a sample application with a --version CLI flag, organized version variables in a dedicated sub-package, automated the build with a Makefile and GitHub Actions, and compared the ldflags approach with debug.ReadBuildInfo from the runtime/debug package.

Adding version metadata to your build workflow gives you reliable traceability across development, staging, and production environments. The ldflags pattern is straightforward, works with every Go version, and integrates into any CI/CD pipeline.

If you would like to learn more about the Go programming language, check out the full How To Code in Go series. For cross-platform builds, see Building Go Applications for Different Operating Systems and Architectures. To learn about Go’s package initialization order, read Understanding init in Go.

<$>[info] Build and deploy on DigitalOcean: Use DigitalOcean App Platform to deploy your Go applications directly from a GitHub repository with automatic builds and managed infrastructure. You can pass ldflags in your build command configuration. Get started with App Platform today.

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.