Containerizing an HTTP Server from Go Source into a Minimal Docker Image

First, compile the HTTP server script into a binary that can run under Linux.

zhaojinqiao@B-LBE6LVDL-1748 03 % ls
httpserver	httpserver.go	httpsev		main		main.go
zhaojinqiao@B-LBE6LVDL-1748 03 % env GOOS=linux go build -o httpser httpserver.go
zhaojinqiao@B-LBE6LVDL-1748 03 % ls
httpser		httpserver	httpserver.go	httpsev		main		main.go

Next, copy the httpsev binary to a folder named httpserver on a Linux evnironment.

Directory with httpsev file

Dockerfile contents:

FROM ubuntu:latest
COPY httpsev /httpsev
EXPOSE 80
ENTRYPOINT /httpsev

Build the image:

docker build -t http .

Docker build log

Enter the container with an interactive shell:

docker run -it --entrypoint /bin/sh http:latest

Shell inside container

Start the HTTP service inside the container.

Starting HTTP server inside container

Access the HTTP service from the host:

  • docker ps (find the container ID)
  • docker inspect cf70f0434fa1 | grep -i pid
  • nsenter -t 16377 -n ip a
  • curl 172.17.0.20:8000

Curl output


A Better Approach: Multi-Stage Build with Scratch

The Go HTTP server source code (main.go):

package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	"strings"
)

func handleRoot(w http.ResponseWriter, r *http.Request) {
	os.Setenv("APP_VERSION", "v0.0.1")
	appVersion := os.Getenv("APP_VERSION")
	w.Header().Set("APP_VERSION", appVersion)
	fmt.Printf("app version: %s \n", appVersion)

	for key, values := range r.Header {
		for _, val := range values {
			fmt.Printf("Header key: %s, Header value: %s \n", key, val)
			w.Header().Set(key, val)
		}
	}

	clientAddr := extractClientIP(r)
	log.Printf("Success! Response code: %d", 200)
	log.Printf("Success! clientip: %s", clientAddr)
}

func healthCheck(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "operational")
}

func extractClientIP(r *http.Request) string {
	forwarded := r.Header.Get("X-Real-IP")
	if forwarded != "" {
		return forwarded
	}
	if addr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
		return addr
	}
	return ""
}

func main() {
	router := http.NewServeMux()
	router.HandleFunc("/debug/pprof/", pprof.Index)
	router.HandleFunc("/debug/pprof/profile", pprof.Profile)
	router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	router.HandleFunc("/debug/pprof/trace", pprof.Trace)
	router.HandleFunc("/", handleRoot)
	router.HandleFunc("/healthz", healthCheck)

	if err := http.ListenAndServe(":8080", router); err != nil {
		log.Fatalf("start http server failed, error: %s\n", err.Error())
	}
}

Multi-stage Dockerfile:

FROM golang:1.17 AS builder

ENV GO111MODULE=off \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOARCH=amd64

WORKDIR /build
COPY . .
RUN go build -o httpserver .

FROM scratch
COPY --from=builder /build/httpserver /
EXPOSE 8080
ENTRYPOINT ["/httpserver"]

Place both files in the same directory.

Files in directory

Build the image:

docker build -t httpserver:0.0.3 .

Docker build success

The Docker image is now ready.

Tags: docker Go Multi-Stage Build Scratch Image HTTP Server

Posted on Sat, 03 Oct 2026 16:33:04 +0000 by ecxzqute