Golang by Example

Logo

Containerization

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.

Sample Application



  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.

Creating a Dockerfile



  # 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:

Building the Docker Image

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.

Multi Stage Build

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:

  1. The first stage uses the Golang image to build the application. It copies the source code and builds the executable.
  2. The second stage uses a lightweight Alpine image to run the application. It copies only the built executable from the first stage, resulting in a smaller final image size.

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

Running the Docker Container

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".

Pushing to Docker Hub

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 container

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:

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.

Quiz Time

💡 Quiz
Why containerization is important?
💡 Quiz
What is the purpose of the Dockerfile?
💡 Quiz
What is the advantage of using a multi-stage build in Docker?

Table of contents

Hello World - Begin with the classic Hello World program
Primitives - Learn about the basic data types in Go
Flow Control - Controlling the flow of your program
Struct - All about struct
Functions - Define and call functions
Methods and Interfaces - Methods and interfaces in Go
Error Handling - Handling errors idiomatically
Concurrency - Goroutines and channels
Anti Patterns - Anti patterns in Go
Libraries - Standard library and third-party libraries
Testing - Writing unit tests with `go test`
Benchmarking - Performance benchmarks
Containerization - Dockerize your Go app
What's Next? - Explore the next steps in your Golang journey


Share to: