All this time we have been running our Go programs on our local machine using go run
or go build
command. But what if we want to run them in a different environment, such as a server or a cloud platform? This is where containerization comes in.
Containerization is the process of packaging an application and its dependencies into a single unit called a container. Containers are lightweight, portable, and can run on any platform that supports containerization, such as Docker. This makes it easy to deploy and run applications in different environments without worrying about compatibility issues.
In this section, we will explore how to containerize a Go application using Docker. We will create a simple Go application, write a Dockerfile to build the container image, and run the container.
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
http.ListenAndServe(":8080", r)
}
This is a simple Go application that uses the Chi web framework to create a REST API endpoint. The /ping
endpoint returns a JSON response with a message.
# Use the official Golang image as the base image
FROM golang:1.16
# Set the working directory inside the container
WORKDIR /app
# Copy the Go module files
COPY go.mod go.sum ./
# Download the Go module dependencies
RUN go mod download
# Copy the rest of the application code
COPY . .
# Build the Go application
RUN go build -o main .
# Expose the port the app runs on
EXPOSE 8080
# Command to run the executable
CMD ["./main"]
This Dockerfile does the following:
/app
.go.mod
and go.sum
) to the working directory.go mod download
.go build
.To build the Docker image, run the following command in the directory where the Dockerfile is located:
docker build -t my-go-app .
This command builds the Docker image and tags it as my-go-app
.
In the previous example, we used a single stage to build and run the Go application. However, this can result in a larger container image size because it includes the entire Go toolchain and source code. To reduce the image size, we can use a multi-stage build. This allows us to separate the build environment from the runtime environment.
# Stage 1: Build
FROM golang:1.16 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .
# Stage 2: Run
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
In this multi-stage build, we have two stages:
In my machine, the image size is reduced from 799 MB to only 15.3 MB. Very significant, right? 🧐
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-go-app latest e57ac7fbd945 14 seconds ago 799MB
my-go-app-multi latest f739515fb292 4 seconds ago 15.3MB
To run the Docker container, use the following command:
docker run -p 8080:8080 my-go-app
This command runs the container and maps port 8080 on the host to port 8080 in the container. You can access the application by visiting http://localhost:8080/ping
in your web browser.
You should see a JSON response with the message "pong".
To share your Docker image with others, you can push it to Docker Hub. First, you need to create a Docker Hub account and log in using the following command:
docker login
Then, tag your image with your Docker Hub username and push it:
docker tag my-go-app yourusername/my-go-app
docker push yourusername/my-go-app
This command tags the image with your Docker Hub username and pushes it to your Docker Hub repository.
Nix is a powerful package manager that allows you to create reproducible builds and environments. It can be used to create containers for your Go applications, ensuring that the same environment is used for development, testing, and production. What makes it different from Docker is that Nix uses a declarative approach to define the environment, which means you can specify the exact dependencies and versions you need in a single file.
To create a Nix container for your Go application, you need to create a default.nix
file in the root of your project:
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
name = "my-go-app";
src = ./.;
buildInputs = [ pkgs.go ];
buildPhase = ''
export HOME=$TMPDIR
mkdir -p $out/bin
go build -o $out/bin/my-go-app
'';
installPhase = ''
# Nothing needed here as we built directly to $out/bin
'';
}
This default.nix
file does the following:
HOME
environment variable to a temporary directory and builds the Go application.pkgs.stdenv.mkDerivation
function is used to create a derivation, which is a Nix expression that describes how to build a package.buildInputs
attribute specifies the dependencies needed to build the package, in this case, the Go package.buildPhase
attribute defines the commands to run during the build phase. In this case, it sets the HOME
environment variable to a temporary directory and builds the Go application using go build
.installPhase
attribute is empty because we built the application directly to the output directory.To build the Nix container, run the following command:
nix-build
This will generate a result directory containing the built application. You can run the application using the following command:
./result/bin/my-go-app
This will run the Go application inside the Nix container. You can see that using Nix, you can create a reproducible build environment for your Go application without the need for Docker. This is particularly useful for projects that require specific versions of dependencies or have complex build requirements.