{"id":510,"date":"2025-08-07T12:56:12","date_gmt":"2025-08-07T12:56:12","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=510"},"modified":"2025-08-07T12:56:12","modified_gmt":"2025-08-07T12:56:12","slug":"dockerize-a-golang-application","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=510","title":{"rendered":"How to Dockerize a Golang Application (Step-by-Step Guide)"},"content":{"rendered":"<p>In this blog, you will learn to Dockerize a Golang application. It is a step by step beginner-friendly tutorial.<\/p>\n<p>By the end of this blog, you will have learned.<\/p>\n<ol>\n<li>How to create a simple Golang application<\/li>\n<li>Dockerize Golang application using scratch and distroless base images.<\/li>\n<li>How to run and test the Dockerized application.<\/li>\n<li>How to scan the Dockerfile using <a href=\"https:\/\/devopscube.com\/lint-dockerfiles-using-hadolint\/\" rel=\"noreferrer\">Hadolint<\/a><\/li>\n<li>How to scan the container image using Trivy<\/li>\n<\/ol>\n<p>Lets get started.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<ol>\n<li><a href=\"https:\/\/devopscube.com\/golang-for-devops\/\" rel=\"noreferrer\">Golang<\/a> [Local Workstation]<\/li>\n<li><a href=\"https:\/\/devopscube.com\/what-is-docker\/\" rel=\"noreferrer\">Docker<\/a> [Local Workstation]<\/li>\n<\/ol>\n<p>Once you ensure all the requirements are available, we can start building the application.<\/p>\n<h2 id=\"steps-to-dockerize-a-golang-application\">Steps to Dockerize a Golang Application<\/h2>\n<p>Follow the steps to Dockerize a Golang application.<\/p>\n<h2 id=\"step-1-clone-the-repo\">Step 1: Clone the Repo<\/h2>\n<p>I have stored this tutorial&#8217;s contents on the public repository, you can clone and use it if required.<\/p>\n<pre><code class=\"language-bash\">git clone https:\/\/github.com\/techiescamp-docker-images\/dockerize-go-web-app.git<\/code><\/pre>\n<p>The following is the structure of the cloned repository.<\/p>\n<pre><code class=\"language-bash\">cd dockerize-go-web-app\/hello-world<\/code><\/pre>\n<pre><code class=\"language-bash\">.\n\u251c\u2500\u2500 Dockerfile\n\u251c\u2500\u2500 go.mod\n\u251c\u2500\u2500 index.html\n\u2514\u2500\u2500 main.go<\/code><\/pre>\n<p>Now, we can start creating a simple Golang application.<\/p>\n<h2 id=\"step-2-create-a-golang-application\">Step 2: Create a Golang Application<\/h2>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> If you have an existing Golang application, skip this step and move to the step 5 to Dockerize it directly.<\/div>\n<\/div>\n<p>First, you need to create a directory named <code>go-web-app<\/code> and initialize the Go module.<\/p>\n<pre><code class=\"language-bash\">go mod init go-app<\/code><\/pre>\n<p>This will create a module file and create a main file named <code>main.go<\/code> for our application.<\/p>\n<p>Add the following contents to the file.<\/p>\n<pre><code class=\"language-bash\">package main\n\nimport (\n    \"io\/ioutil\"\n    \"log\"\n    \"net\/http\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    \/\/ Read the HTML file\n    html, err := ioutil.ReadFile(\"index.html\")\n    if err != nil {\n        http.Error(w, \"Could not read HTML file\", http.StatusInternalServerError)\n        return\n    }\n\n    \/\/ Set the content type to HTML and write the HTML content\n    w.Header().Set(\"Content-Type\", \"text\/html\")\n    w.Write(html)\n}\n\nfunc main() {\n    http.HandleFunc(\"\/\", handler)\n    log.Println(\"Server is listening on port 8080\")\n    if err := http.ListenAndServe(\":8080\", nil); err != nil {\n        log.Fatalf(\"Failed to start server: %s\", err)\n    }\n}<\/code><\/pre>\n<p>To create this application, I have used these three packages.<\/p>\n<ul>\n<li><strong>io\/ioutil <\/strong>&#8211; This package is used to read\/write files. I am using a custom HTML page for the output.<\/li>\n<li><strong>log <\/strong>&#8211; This package is used to generate logs based on the errors and status.<\/li>\n<li><strong>net\/http<\/strong> &#8211; This is the major package that handles the <strong>HTTP<\/strong> requests and responses of our web application.<\/li>\n<\/ul>\n<h2 id=\"step-3-create-the-html-file\">Step 3: Create the HTML File<\/h2>\n<p>Create an HTML file (<code>index.html<\/code>) for our web application and add the following contents.<\/p>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n    &lt;meta charset=\"UTF-8\"&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;\n    &lt;title&gt;Hello, World!&lt;\/title&gt;\n    &lt;link href=\"https:\/\/fonts.googleapis.com\/css2?family=Roboto:wght@300&amp;display=swap\" rel=\"stylesheet\"&gt;\n    &lt;style&gt;\n        body {\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            height: 100vh;\n            margin: 0;\n            background: linear-gradient(135deg, #f5f7fa, #c3cfe2);\n            font-family: 'Roboto', sans-serif;\n        }\n        .hello {\n            font-size: 3em;\n            text-align: center;\n            color: #333;\n            padding: 20px;\n            border-radius: 10px;\n            background: rgba(255, 255, 255, 0.8);\n            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n        }\n    &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;div class=\"hello\"&gt;\n        &lt;p&gt;Hello, World!&lt;\/p&gt;\n    &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<p>Once the main Go file and HTML file are ready, we can test them from our local machine<\/p>\n<h2 id=\"step-4-run-the-application-locally\">Step 4: Run the Application Locally<\/h2>\n<p>Before we run the application, we need to compile the code.<\/p>\n<p>To compile and run the application, use the following command.<\/p>\n<pre><code class=\"language-bash\">go run main.go<\/code><\/pre>\n<p>Once the app starts running, we can access the web page from our local machine.<\/p>\n<p>For that, open any browser and paste the URL <code>http:\/\/localhost:8080\/<\/code><\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-40.png\" class=\"kg-image\" alt=\"the web page of the golang application\" loading=\"lazy\" width=\"1608\" height=\"992\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-40.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/08\/image-40.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/08\/image-40.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-40.png 1608w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, we confirmed that our application is running without any issues, so we can start containerizing the application.<\/p>\n<h2 id=\"step-5-create-a-dockerfile-for-the-go-application\">Step 5: Create a Dockerfile for the Go Application<\/h2>\n<p>We need to <a href=\"https:\/\/devopscube.com\/build-docker-image\/\" rel=\"noreferrer\">build a Docker image<\/a> using a <strong>multi-stage<\/strong> build to <a href=\"https:\/\/devopscube.com\/reduce-docker-image-size\/\" rel=\"noreferrer\">optimize the Docker image.<\/a><\/p>\n<p>We will look at both scratch image and distroless image options.<\/p>\n<h3 id=\"golang-image-using-scratch-image\">Golang image using scratch image<\/h3>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">A <b><strong style=\"white-space: pre-wrap;\">scratch image<\/strong><\/b> in Docker is basically an <b><strong style=\"white-space: pre-wrap;\">empty base image<\/strong><\/b>. It is the starting point when you dont want to use any existing operating system image like <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">alpine<\/code> or <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">ubuntu<\/code>. Instead, you build your image from <b><strong style=\"white-space: pre-wrap;\">nothing<\/strong><\/b>, only adding the files you need.<\/p>\n<p>It works well with Golang because, Go programs can be compiled into a <b><strong style=\"white-space: pre-wrap;\">self-contained static binary<\/strong><\/b> that doesn\u2019t need any operating system packages.<\/div>\n<\/div>\n<p>Create a <code>Dockerfile<\/code> and add the following contents.<\/p>\n<pre><code class=\"language-docker\"># Stage 1: Build the Go binary\nFROM golang:1.21-alpine AS builder\n\nRUN adduser -D builderuser\n\nWORKDIR \/app\n\n\nCOPY main.go .\nCOPY index.html .\n\n# Use go modules if needed \n# COPY go.mod go.sum .\/\n# RUN go mod download\n\n\nRUN CGO_ENABLED=0 GOOS=linux go build -o server main.go\n\n# Stage 2: Minimal secure runtime\nFROM scratch\n\nWORKDIR \/app\n\nCOPY --from=builder \/app\/server .\nCOPY --from=builder \/app\/index.html .\n\nUSER 1000\n\nEXPOSE 8080\n\nENTRYPOINT [\".\/server\"]<\/code><\/pre>\n<p>Now, we have created the <code>Dockerfile<\/code>.<\/p>\n<p>The following is the explanation of what we have done in the <code>Dockerfile<\/code> to optimize the build.<\/p>\n<ol>\n<li>In stage 1, we use the official <code>golang:1.21-alpine<\/code> image to download the dependencies and compile the code.<\/li>\n<li>Then we compile go app with <code>CGO_ENABLED=0 GOOS=linux go build<\/code> that produces a single binary file that contains your code, Go runtime and all the dependencies.<\/li>\n<li>In stage 2, we have used a <strong>lightweight Scratch image<\/strong> to run the compiled code from stage 1.<\/li>\n<li>To follow the security best practices, we created a non-root user <code>builduser,<\/code> to run the application inside the container.<\/li>\n<li>Exposing the application on Port <code>8080<\/code> to access the application.<\/li>\n<\/ol>\n<p>Although scratch image are very minimal, it comes with certain disadvantages. For example, it does not include the standard CA certificate bundle (\/etc\/ssl\/certs\/ca-certificates.crt). <\/p>\n<p>Many apps (APIs, web servers, DB clients) need <strong>TLS\/SSL certificates<\/strong> (like root CAs) to make secure HTTPS connections.<\/p>\n<p>That means you must <strong>manually copy certificates<\/strong> into the image and keep them updated.<\/p>\n<p>So a better and safe alternative is Distroless images explained in the next section.<\/p>\n<h3 id=\"golang-image-using-distroless-base-image\">Golang Image using Distroless Base Image<\/h3>\n<p>For enterprise environments, the certificate management complexity alone often makes distroless more practical than true scratch images.<\/p>\n<p>Here is the Dockerfile using <a href=\"https:\/\/github.com\/GoogleContainerTools\/distroless?ref=devopscube.com\" rel=\"noreferrer\">Distroless base image<\/a>.<\/p>\n<pre><code class=\"language-docker\"># Stage 1: Build the Go binary\nFROM golang:1.21-alpine AS builder\nWORKDIR \/app\n\nCOPY main.go .\nCOPY index.html .\n\nRUN CGO_ENABLED=0 GOOS=linux go build -ldflags=\"-s -w\" -o server main.go\n\n# Stage 2: Distroless runtime with basic OS data\nFROM gcr.io\/distroless\/base\nWORKDIR \/app\n\nCOPY --from=builder \/app\/server \/app\/server\nCOPY --from=builder \/app\/index.html \/app\/index.html\n\nUSER nonroot:nonroot\n\nEXPOSE 8080\nENTRYPOINT [\"\/app\/server\"]<\/code><\/pre>\n<h2 id=\"step-6-create-a-container-image\">Step 6: Create a Container Image<\/h2>\n<p>To create the container image, use the following command with any of the Dockerfiles from above.<\/p>\n<pre><code class=\"language-bash\">sudo docker build -t go-web-app .<\/code><\/pre>\n<p><code>go-web-app<\/code> is the image name, you can change it if required.<\/p>\n<p>To list the available images, use the following command.<\/p>\n<pre><code class=\"language-bash\">$ sudo docker images\n\nREPOSITORY                   TAG       IMAGE ID       CREATED         SIZE\ngo-web-app                   latest    fbfe728d2742   2 minutes ago   6.72MB\n<\/code><\/pre>\n<p>Once the container image is created, it is better to push it to a repository like Docker Hub or JFrog.<\/p>\n<p>Now, the container image is ready, so we can run the container.<\/p>\n<h2 id=\"step-7-run-containerized-go-application\">Step 7: Run Containerized Go Application<\/h2>\n<p>The Go web application container image is ready, so we can create a container with this image.<\/p>\n<pre><code class=\"language-bash\">sudo docker run -d --name go-app -p 8080:8080  go-web-app<\/code><\/pre>\n<ul>\n<li><code>go-app<\/code> &#8211; Name of the container<\/li>\n<li><code>-p 8080:8080<\/code> &#8211; Map host port 8080 to the container port 8080<\/li>\n<li><code>go-web-app<\/code> &#8211; Name of the container image that we built.<\/li>\n<\/ul>\n<p>To list the running containers, use the following command.<\/p>\n<pre><code class=\"language-bash\">$ sudo docker ps\n\nCONTAINER ID   IMAGE        COMMAND      CREATED         STATUS         PORTS                    NAMES\n7f4d904b3f84   go-web-app   \".\/server\"   6 minutes ago   Up 6 minutes   0.0.0.0:8080-&gt;8080\/tcp   go-app<\/code><\/pre>\n<p>Now, we can try to access our application using the URL.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-40.png\" class=\"kg-image\" alt=\"the web page of the golang application\" loading=\"lazy\" width=\"1608\" height=\"992\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/08\/image-40.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/08\/image-40.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/08\/image-40.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/08\/image-40.png 1608w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Here, we have used <code>localhost<\/code> because the application is running on our local Docker engine, but if you are using a cloud instance like EC2, then you can use the IP address of the instance instead of localhost.<\/p>\n<p>Now, our deployment is completed, though we need to follow the security practices if we are doing this for an organization or in a real project.<\/p>\n<h2 id=\"container-security-best-practices\">Container Security Best Practices<\/h2>\n<p>Vulnerabilities can be present in both the <a href=\"https:\/\/devopscube.com\/create-dockerfile-using-docker-init\/\" rel=\"noreferrer\">Dockerfile<\/a> and the container images, so we need to scan them both. <\/p>\n<h3 id=\"scan-the-dockerfile\">Scan the Dockerfile<\/h3>\n<p>To scan the <strong>Dockerfile<\/strong> and identify the misconfiguration, we are using a linting tool, which is <strong>Hadolint.<\/strong><\/p>\n<p>To install the Hadolint on your machine and learn more about it, refer to this <a href=\"https:\/\/devopscube.com\/lint-dockerfiles-using-hadolint\/\">blog.<\/a><\/p>\n<p>Currently, our Dockerfile doesn&#8217;t have any issues, though I have made some intentional misconfiguration for the demo<\/p>\n<pre><code>hadolint Dockerfile<\/code><\/pre>\n<p>The output will prompt where the changes are required, and if your Dockerfile doesn&#8217;t have any issues, you will not see any output.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-71.png\" class=\"kg-image\" alt=\"hadolint output\" loading=\"lazy\" width=\"925\" height=\"327\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-71.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-71.png 925w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now, we can scan the container image.<\/p>\n<h3 id=\"scan-container-image\">Scan Container Image<\/h3>\n<p>To identify the vulnerabilities of our container, we can use the <strong>Trivy<\/strong> tool.<\/p>\n<p>To install <a href=\"https:\/\/devopscube.com\/trivy-security-scanner\/\">Trivy<\/a> and to know about its use cases, you can refer to this <a href=\"https:\/\/devopscube.com\/scan-docker-images-using-trivy\/\">blog.<\/a><\/p>\n<p>After installing the utility, use the following command to identify the vulnerabilities of the container image.<\/p>\n<pre><code>sudo trivy image go-web-app<\/code><\/pre>\n<p>This will list the vulnerabilities based on the severity levels.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-72.png\" class=\"kg-image\" alt=\"trivy scan report\n\" loading=\"lazy\" width=\"1281\" height=\"734\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-72.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-72.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-72.png 1281w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>This is how we containerize a Go application.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>We have successfully containerized our Go web application. This helps you understand how to containerize your own Golang applications.<\/p>\n<p>There is more you can do with that, and don&#8217;t forget to store the container image in a container repo such as Dockerhub or ECR.<\/p>\n<p>Always follow the security best practices when you containerize the application, and you can also integrate these scanning tools into your <a href=\"https:\/\/devopscube.com\/learning-ci-cd-tools\/\" rel=\"noreferrer\">CI\/CD pipelines<\/a> to automate the process.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/dockerize-a-golang-application\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Dockerize a Golang Application (Step-by-Step Guide) \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/dockerize-a-golang-application\/<\/p>\n","protected":false},"author":1,"featured_media":511,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-510","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/510","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=510"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/510\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/511"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=510"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=510"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=510"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}