Joel Dare

Small Go Binaries

Go binaries are actually rather small.

I’ve avoided learning the Go language for a while. A couple of my co-workers have been excited about it and have encouraged me to give it a try. But, my understanding was that a “hello world” application in Go was actually pretty large. I ran a couple quick tests to see the size of the compiled application and then decided Go wasn’t something I wanted to learn right then. I’m a big fan of minimalism and small binaries and so it didn’t appeal to me.

After reading a blog post from an author that tried to get his Docker container to the smallest size possible, I changed my mind. The author selected Go as the language and was able to get the container to 13Mb before moving away from Go to reduce the size even more. After reading that, I thought I should give the language another look.

I decided to build a simple web server with SQLite3. I thought that was about as minimal as I could go and still feel like I could produce a full web API.

Here are the size results.

Go Binary Sizes

So, even the standard compiled binary (with a couple modules) is only 11Mb. That’s not bad in today’s world. And, if you want to reduce that size even more, you can get it down to 7.7Mb by removing debug information and even down to 2.2Mb if you compress the executable.

Keep in mind that the compressed version will need to be decompressed each time it’s run. That’s fine for a modern server but might not be a good idea in some environments, such as an embedded system that doesn’t have a lot of CPU power. It’s probably completely unnecessary for most modern systems.

Here are the exact commands I ran to build and list binary sizes.

$ go build hello.go
$ ls -lh hello
-rwxr-xr-x  1 jdare  staff    11M Apr 23 10:16 hello
$ go build -ldflags="-s -w" hello.go
$ ls -lh hello
-rwxr-xr-x  1 jdare  staff   7.3M Apr 23 10:17 hello
$ upx --brute hello
$ ls -lh hello
-rwxr-xr-x  1 jdare  staff   2.2M Apr 23 10:17 hello

Here’s the naive code I used for this simple test.

package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	// Create a database an add a record
	database, _ :=
		sql.Open("sqlite3", "./users.db")
	statement, _ :=
		database.Prepare("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)")
	statement.Exec()
	statement, _ =
		database.Prepare("INSERT INTO users (email) VALUES (?)")
	statement.Exec("joel@joeldare.com")
	// Get read to start a file server
	fileServer := http.FileServer(http.Dir("./"))
	http.Handle("/", fileServer)
	// Tell the world hello
	fmt.Printf("Starting server\n")
	// Listen or error
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

Published by Joel Dare on April 23rd, 2021.