Skip to main content

Embedding Version in Binaries

The real power of versionator isn't just tracking version in source control—it's getting that version into your compiled binary so you can always identify exactly what's running in production.

Why Binary Embedding Matters

Consider this scenario: You're debugging a production issue at 2 AM. You SSH into a server and find a binary called myapp. What version is it?

Without embedded version info:

$ ./myapp --version
# ???
$ ls -la myapp
-rwxr-xr-x 1 deploy deploy 12345678 Jan 15 10:30 myapp
# Timestamp? Maybe helpful, maybe not.

With embedded version info:

$ ./myapp --version
myapp v2.1.3 (commit: abc1234, built: 2024-01-15T10:30:00Z)

You instantly know: the exact version, the exact commit, and when it was built.

Two Approaches

Every language falls into one of two categories:

CategoryLanguagesMechanism
CompiledGo, Rust, C, C++, Java, Kotlin, C#, SwiftInject values at compile time
InterpretedPython, JavaScript, TypeScript, Ruby, PHPGenerate source file at build time

Both approaches achieve the same result: version info baked into the final artifact.


Live Demos

All examples below are runnable. Each includes a justfile with just run to see the embedded version in action.

Go

Location: examples/go/

Go's linker injects string values via -ldflags:

examples/go/main.go
package main

import "fmt"

// VERSION will be set by the linker during build
var VERSION = "0.0.0"

func main() {
fmt.Printf("Sample Go Application\n")
fmt.Printf("Version: %s\n", VERSION)
}
examples/go/Makefile (excerpt)
build:
VERSION=$$(versionator version); \
go build -ldflags "-X main.VERSION=$$VERSION" -o sample-app .

Run it:

$ cd examples/go && just run
Getting version from versionator...
Building sample application with version: 0.0.13
Build completed: sample-app
./sample-app
Sample Go Application
Version: 0.0.13

Source code: main.go | justfile


Rust

Location: examples/rust/

Rust reads environment variables at compile time with option_env!():

examples/rust/main.rs
fn main() {
// VERSION will be set by the compiler during build via environment variable
let version = option_env!("VERSION").unwrap_or("0.0.0");

println!("Sample Rust Application");
println!("Version: {}", version);
}
examples/rust/Makefile (excerpt)
build:
VERSION=$$(versionator version); \
VERSION="$$VERSION" rustc -o sample-app main.rs

Run it:

$ cd examples/rust && just run
Getting version from versionator...
Building sample application with version: 0.0.13
Build completed: sample-app
./sample-app
Sample Rust Application
Version: 0.0.13

Source code: main.rs | justfile


C

Location: examples/c/

C uses preprocessor defines (-D) to inject values:

examples/c/main.c
#include <stdio.h>

// VERSION will be set by the compiler during build
#ifndef VERSION
#define VERSION "0.0.0"
#endif

int main() {
printf("Sample C Application\n");
printf("Version: %s\n", VERSION);
return 0;
}
examples/c/Makefile (excerpt)
build:
VERSION=$$(versionator version); \
gcc -DVERSION="\"$$VERSION\"" -o sample-app main.c

Run it:

$ cd examples/c && just run
Getting version from versionator...
Building sample application with version: 0.0.13
Build completed: sample-app
./sample-app
Sample C Application
Version: 0.0.13

Source code: main.c | justfile


C++

Location: examples/cpp/

Same approach as C—preprocessor defines:

examples/cpp/main.cpp
#include <iostream>

// VERSION will be set by the compiler during build
#ifndef VERSION
#define VERSION "0.0.0"
#endif

int main() {
std::cout << "Sample C++ Application" << std::endl;
std::cout << "Version: " << VERSION << std::endl;
return 0;
}
examples/cpp/Makefile (excerpt)
build:
VERSION=$$(versionator version); \
g++ -DVERSION="\"$$VERSION\"" -o sample-app main.cpp

Run it:

$ cd examples/cpp && just run
Getting version from versionator...
Building sample application with version: 0.0.13
Build completed: sample-app
./sample-app
Sample C++ Application
Version: 0.0.13

Source code: main.cpp | justfile


Java

Location: examples/java/

Java generates a source file from a template at build time:

examples/java/app/Main.java
package app;

import static app.BuildTime.VERSION;

public class Main {
public static void main(String[] args) {
System.out.println("Sample Java Application");
System.out.println("Version: " + VERSION);
}
}

The Makefile generates BuildTime.java from a template:

examples/java/Makefile (excerpt)
build:
VERSION=$$(versionator version); \
sed -e "s/@VERSION@/$${VERSION}/g" BuildTime.java.tmpl > BuildTime.java; \
javac Main.java BuildTime.java

Run it:

$ cd examples/java && just run
Getting version from versionator...
Generating BuildTime.java from template...
Building sample application with version: 0.0.13
Build completed: app/Main.class app/BuildTime.class
java app.Main
Sample Java Application
Version: 0.0.13

Source code: app/Main.java | app/BuildTime.tmpl.java | justfile


Kotlin

Location: examples/kotlin/

Kotlin generates a Version.kt object at build time:

examples/kotlin/Main.kt
package app

import version.Version

fun main() {
println("Sample Kotlin Application")
println("Version: ${Version.VERSION}")
}
examples/kotlin/Makefile (excerpt)
version-file:
versionator output emit kotlin --output Version.kt

build: version-file
kotlinc Main.kt Version.kt -include-runtime -d sample-app.jar

Run it:

$ cd examples/kotlin && just run
Generating Version.kt using versionator emit...
Building Kotlin application...
Build completed: sample-app.jar
java -jar sample-app.jar
Sample Kotlin Application
Version: 0.0.16

Source code: Main.kt | justfile


C#

Location: examples/csharp/

C# generates a Version.cs static class at build time:

examples/csharp/Program.cs
using Version;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Sample C# Application");
Console.WriteLine($"Version: {VersionInfo.Version}");
}
}
examples/csharp/Makefile (excerpt)
version-file:
versionator output emit csharp --output Version.cs

build: version-file
dotnet build -c Release -o out

Run it:

$ cd examples/csharp && just run
Generating Version.cs using versionator emit...
Building C# application...
Build completed: out/SampleApp.dll
dotnet out/SampleApp.dll
Sample C# Application
Version: 0.0.16

Source code: Program.cs | justfile


Swift

Location: examples/swift/

Swift generates a Version.swift file with global constants:

examples/swift/main.swift
print("Sample Swift Application")
print("Version: \(VERSION)")
examples/swift/Makefile (excerpt)
version-file:
versionator output emit swift --output Version.swift

build: version-file
swiftc -o sample-app main.swift Version.swift

Run it:

$ cd examples/swift && just run
Generating Version.swift using versionator emit...
Building Swift application...
Build completed: sample-app
./sample-app
Sample Swift Application
Version: 0.0.16

Source code: main.swift | justfile


Python

Location: examples/python/

Python uses versionator output emit to generate a _version.py module:

examples/python/mypackage/main.py
"""Sample application entry point."""

from . import __version__


def main():
print("Sample Python Application")
print(f"Version: {__version__}")


if __name__ == "__main__":
main()
examples/python/Makefile (excerpt)
version-file:
versionator output emit python --output mypackage/_version.py

run: version-file
python -m mypackage.main

Run it:

$ cd examples/python && just run
Generating _version.py using versionator emit...
Version 0.0.13 written to mypackage/_version.py
python3 -m mypackage.main
Sample Python Application
Version: 0.0.13

Source code: mypackage/main.py | justfile


JavaScript

Location: examples/javascript/

JavaScript generates a version.js module:

examples/javascript/src/index.js
import { VERSION } from './version.js';

function main() {
console.log('Sample JavaScript Application');
console.log(`Version: ${VERSION}`);
}

main();
examples/javascript/Makefile (excerpt)
version-file:
versionator output emit js --output src/version.js

run: version-file
node src/index.js

Run it:

$ cd examples/javascript && just run
Generating version.js using versionator emit...
Version 0.0.13 written to src/version.js
node src/index.js
Sample JavaScript Application
Version: 0.0.13

Source code: src/index.js | justfile


TypeScript

Location: examples/typescript/

TypeScript generates a typed version.ts module:

examples/typescript/src/index.ts
import { VERSION } from './version.js';

function main(): void {
console.log('Sample TypeScript Application');
console.log(`Version: ${VERSION}`);
}

main();
examples/typescript/Makefile (excerpt)
version-file:
versionator output emit ts --output src/version.ts

build: version-file
npx tsc

run: build
node dist/index.js

Run it:

$ cd examples/typescript && just run
Generating version.ts using versionator emit...
Version 0.0.13 written to src/version.ts
Building TypeScript package...
Build completed!
node dist/index.js
Sample TypeScript Application
Version: 0.0.13

Source code: src/index.ts | justfile


Ruby

Location: examples/ruby/

Ruby generates a version.rb module with a Versionator namespace:

examples/ruby/lib/mypackage.rb
require_relative "mypackage/version"

module Mypackage
def self.hello
puts "Sample Ruby Application"
puts "Version: #{Versionator::VERSION}"
end
end
examples/ruby/Makefile (excerpt)
version-file:
versionator output emit ruby --output lib/mypackage/version.rb

run: version-file
ruby -I lib -e "require 'mypackage'; Mypackage.hello"

Run it:

$ cd examples/ruby && just run
Generating version.rb using versionator emit...
Version 0.0.13 written to lib/mypackage/version.rb
ruby -I lib -e "require 'mypackage'; Mypackage.hello"
Sample Ruby Application
Version: 0.0.13

Source code: lib/mypackage.rb | justfile


PHP

PHP generates a version class:

versionator output emit php --output src/Version.php

Generated file:

<?php
namespace MyApp;

class Version {
public const VERSION = "1.2.3";
public const MAJOR = 1;
public const MINOR = 2;
public const PATCH = 3;
}

JSON / YAML

For configuration files or API responses:

# JSON
versionator output emit json --output version.json

# YAML
versionator output emit yaml --output version.yml

JSON output:

{
"version": "1.2.3",
"major": 1,
"minor": 2,
"patch": 3
}

Docker / Containers

Location: examples/docker/

Container images embed version info in two places:

  1. The binary inside (using the language-specific approach above)
  2. OCI image labels (for image inspection without running)
examples/docker/Dockerfile
# Build arguments
ARG VERSION=dev
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown

FROM golang:1.21-alpine AS builder

ARG VERSION
ARG GIT_COMMIT
ARG BUILD_DATE

WORKDIR /app
COPY . .

# Inject version at compile time
RUN go build -ldflags "\
-X main.Version=${VERSION} \
-X main.GitCommit=${GIT_COMMIT} \
-X main.BuildDate=${BUILD_DATE}" \
-o /app/sample-app

FROM alpine:3.19

ARG VERSION
ARG GIT_COMMIT
ARG BUILD_DATE

# OCI Image Labels
LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"

COPY --from=builder /app/sample-app /usr/local/bin/sample-app

ENTRYPOINT ["sample-app"]
examples/docker/Makefile (excerpt)
docker-build:
VERSION=$$(versionator version); \
COMMIT=$$(versionator output version -t "{{ShortHash}}"); \
DATE=$$(versionator output version -t "{{BuildDateTimeUTC}}"); \
docker build \
--build-arg VERSION=$$VERSION \
--build-arg GIT_COMMIT=$$COMMIT \
--build-arg BUILD_DATE=$$DATE \
-t sample-app:$$VERSION .

Run it:

$ cd examples/docker && just show-version
Version from versionator:
VERSION=0.0.13
GIT_COMMIT=ba4ecb3
BUILD_DATE=2026-03-08T18:52:29Z

$ just docker-build
Building Docker image with:
VERSION=0.0.13
GIT_COMMIT=ba4ecb3
BUILD_DATE=2026-03-08T18:52:29Z
...

$ just docker-run
Running sample-app:0.0.13
Sample Docker Application
Version: 0.0.13 (commit: ba4ecb3, built: 2026-03-08T18:52:29Z)

Source code: main.go | Dockerfile | justfile


Running All Demos

From the repository root:

# Build versionator first
just build

# Run all examples
for dir in examples/*/; do
echo "=== $dir ==="
(cd "$dir" && just run 2>/dev/null || echo "skipped")
done

The Pattern

Every example follows the same pattern:

  1. Makefile calls versionator output version to get the current version
  2. Build step injects that version (compile-time for compiled languages, file generation for interpreted)
  3. Application displays the embedded version at runtime

The version is baked in. It doesn't read from a file at runtime. It doesn't query an API. It's part of the binary itself.


Template Variables

Use these versionator template variables for richer version info:

VariableUse CaseExample
{{MajorMinorPatch}}Clean version1.2.3
{{Prefix}}{{MajorMinorPatch}}Prefixed versionv1.2.3
{{ShortHash}}Git commit (7 chars)abc1234
{{BuildDateTimeUTC}}ISO 8601 timestamp2024-01-15T10:30:00Z
{{BranchName}}Current branchmain

See Template Variables for the complete reference.


Custom Templates

The examples above use built-in templates via versionator output emit <lang>. For custom namespaces, additional fields, or different file structures, use custom templates with --template-file.

Dump and Customize

# Dump Python template
versionator output emit dump python > custom_python.tmpl

# Edit custom_python.tmpl...

# Use custom template
versionator output emit --template-file custom_python.tmpl --output _version.py

Custom Template Examples

Each interpreted language has a -custom example demonstrating the --template-file workflow:

LanguageBuilt-in TemplateCustom Template
Pythonexamples/python/examples/python-custom/
JavaScriptexamples/javascript/examples/javascript-custom/
TypeScriptexamples/typescript/examples/typescript-custom/
Rubyexamples/ruby/examples/ruby-custom/

Built-in examples use versionator output emit <lang> — simple, zero configuration.

Custom examples use versionator output emit --template-file — for custom namespaces (e.g., Mypackage::VERSION instead of Versionator::VERSION) or additional fields like GIT_HASH and BUILD_DATE.


Best Practices

  1. Add generated files to .gitignore: Don't commit version files
  2. Generate at build time: Run emit in build scripts, not manually
  3. Use appropriate approach: Inject for compiled, generate for interpreted
  4. Include in CI: Ensure version files are generated in CI/CD

See Also