search
HomeBackend DevelopmentGolangHow to Deploy Go Applications With Docker

Docker is a containerization platform that simplifies applications’ packaging, distribution, and deployment. You can harness the benefits of Go and Docker to enhance your applications’ efficiency, portability, and security.

This tutorial is invested in teaching you how you can build and deploy your Go applications with Docker. You’ll learn by building a RESTful API with the Gorilla Mux and GORM packages that you’ll containerize and deploy.

Step 1: Setting Up Your Development Environment

You’d need to have Go and Docker installed on your computer to build and containerize your Go apps with Docker.

Ensure that you have Go and Docker installed on your system. You can download Go from the official Go downloads website and Docker from Docker Hub. Visit the webpage if you haven’t, and follow the installation instructions for your specific operating system.

This article teaches how to deploy Go apps with Docker and teaches you more about installing and setting up Docker and a Postgres database, including containerizing your Go applications.

After installation, configure your Go development environment by setting environment variables and paths as needed. Ensure that you have a working Go workspace with the required directory structure.

Additionally, you can familiarize yourself with Docker's command-line interface (CLI) and basic Docker concepts.

Create a new directory for this project and run the go mod init command to initialize the directory as a Go project.

go mod init

After initializing the Go project, run this command to add the GORM and Gorilla Mux packages as dependencies to your project.

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

You’ll use the Gorilla Mux package for routing. The GORM package provides an interface for you to use Go types for SQL database operations along with the driver package you installed (in this case, Postgres).

Step 2: Building the Go Application

In this tutorial, you’ll use a popular Go layered architecture style and use interfaces to interact with the various components of our app.

Here’s the directory structure of the application.

go mod init
  1. Dockerfile: The Dockerfile is the configuration file for building the Docker image. You’ll write the contents of this file based on the API.
  2. cmd: The cmd directory usually contains the entry point of your application. The server sub-directory suggests that the API server is the main component of your project. The main.go file in the cmd/server contains the application's entry point.
  3. internal: The internal directory helps organize the internal packages of your application. The internal packages shouldn’t be exported, making them an excellent place to hide implementation details.
    • http: This subdirectory will contain HTTP-related code, including route handlers and possibly middleware for your API.
      • handlers.go: You’ll include your HTTP request handlers in this file.
      • users.go: You’ll specify HTTP handlers related to user management here.
    • models: This directory will contain database-related code and data models.
    • database.go: This file will contain code for initializing and connecting to the database.
    • migrations.go: This file handles database schema migrations, ensuring that your database schema matches your application's requirements.
    • users.go: This file will have data models or struct definitions related to users interacting with the database.
    • users: This directory will contain user-specific logic.
      • user.go: This file includes functions and structures related to user management that interact with the database implementation. The HTTP implementation interacts with the functions here.

This project structure appears well-organized, clearly separating concerns between different components. This organization makes it easier to maintain and scale your Go API as it grows.

This isn’t a Go standard. However, many Go developers and open-source projects use this structure for your applications.

Step 2a: Writing the Database Implementation

You’ll set up database functionality for your application. You’ll have to define the models using structs, connect to the database, and set up migrations for your insertion operations on your database.

Here’s the list of imports you’ll need for the database implementation.

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

The first task is defining a strut that matches your database schema for your app. GORM provides tags for specifying additional options and constraints on fields.

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

The User struct represents the model for working with user data in a database.

In your database.go file, declare a struct to encapsulate the database connection instance. You’ll use the struct to connect to your database from other parts of the database implementation package.

go mod init

Next, create a database connection function that connects the database implementation to the database program to the database:

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

The NewDatabase function creates a new Database instance and establishes a connection to the database. It returns a pointer to the Database instance, and an error, if any, occurs during the process.

After a successful database connection, you can set up migration functionality for your database implementation with the function as thus:

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

The MgrateDB function sets up automatic migrations for the User struct with the database client AutoMigrate function and returns an error if there’s any encountered during the process.

Step 2b: Defining Functions for the Database Implementation

In the users.go file where you defined the struct for your database schema, you can proceed to define the functions for the database implementation.

Here are the CreateUser, GetUserByID, UpdateUser, and DeleteUser functions responsible for CRUD operations on the database.

package models

import (
    // imports from the user implementation
    "BetterApp/internal/users"

    "context"
    "gorm.io/gorm"
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm/schema"
    "os"
)

Your user implementation will call these functions to access the database functionality.

Step 2c: Writing the User Implementation

Your user implementation plays a significant role in relaying data from the database to the HTTP implementation.

You’ll define a struct that matches the struct in the database implementation and add JSON tags to the fields for usage; then, you’ll define functions that call the database functions with the data from the HTTP implementation.

Here are the imports you’ll need for your user implementation:

// internal/models/users.go

type User struct {
    gorm.Model
    Username string `gorm:"unique;not null"`
    Email    string `gorm:"unique;not null"`
    IsActive bool   `gorm:"not null"`
}

Here’s the structure with JSON tags. The json:"-" in the gorm.Model field specifies that you want to exclude the field from the JSON operations.

// internal/models/database.go

type Database struct {
    Client *gorm.DB
}

Next, you’ll declare an interface with methods for the user implementation functions, a service struct for the user implementation, and a function that initializes the service implementation.

// internal/models/database.go

func NewDatabase() (*Database, error) {

    // Construct a connection string using environment variables for database configuration.
    configurations := fmt.Sprintf("host=%v port=%v user=%v password=%v dbname=%v sslmode=%v",
        os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USERNAME"),
        os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), os.Getenv("SSL_MODE"))

    // Open a connection to the database using GORM and PostgreSQL driver.
    db, err := gorm.Open(postgres.New(postgres.Config{
        DSN:                  configurations,
        PreferSimpleProtocol: true,
    }), &gorm.Config{NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    }})
    if err != nil {
        return nil, err
    }

    // Enable connection pooling by configuring maximum idle and open connections.
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)

    // Return the Database instance with the established database connection.
    return &Database{
        Client: db,
    }, nil
}

The interface and service will help manage user-related operations outside the user implementation.

Next, you can define methods of the UserService struct implementation that call the database implementation.

// internal/models/migrations.go

func (d *Database) MigrateDB() error {
    log.Println("Database Migration in Process...")

    // Use GORM AutoMigrate to migrate all the database schemas.
    err := d.Client.AutoMigrate(&User{})
    if err != nil {
        return err
    }

    log.Println("Database Migration Complete!")
    return nil
}

The CreateUser, GetUserByID, UpdateUser, and DeleteUser functions are responsible for calling the CRUD operations on the database implementation. The HTTP implementation will call these functions to access the database.

Step 2c: Writing the HTTP Implementation

The HTTP implementation is part of your application that receives and interacts with incoming requests.

Here’s the list of imports you’ll need across your HTTP implementation:

go mod init

First, declare a struct and include a Router instance, a HTTP instance, and an instance of the user service.

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

Then create a function that returns a pointer to the Handler struct, where you can configure the server and handlers.

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

The NewHandler function sets up and configures an HTTP request handler, making it ready to handle incoming HTTP requests for a specific service while also defining server settings and routes.

The mapRoutes function you called in the NewHandler function sets up routes by mapping them to their respective handler functions.

package models

import (
    // imports from the user implementation
    "BetterApp/internal/users"

    "context"
    "gorm.io/gorm"
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm/schema"
    "os"
)

Next, define the handler functions and their functionalities. Here are the CreateUser, GetUserByID, UpdateUser and DeleteUser functions that are responsible for intercepting HTTP requests and responding based on the operation.

// internal/models/users.go

type User struct {
    gorm.Model
    Username string `gorm:"unique;not null"`
    Email    string `gorm:"unique;not null"`
    IsActive bool   `gorm:"not null"`
}

Now, you can write the functionality for starting the server.

// internal/models/database.go

type Database struct {
    Client *gorm.DB
}

The Serve function starts the server on the specified port and returns an error if there’s any during the process.

Step 2d: Coupling the Implementations and Running the Application

Import the implementations in your main.go file to couple the implementations and run your app.

// internal/models/database.go

func NewDatabase() (*Database, error) {

    // Construct a connection string using environment variables for database configuration.
    configurations := fmt.Sprintf("host=%v port=%v user=%v password=%v dbname=%v sslmode=%v",
        os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USERNAME"),
        os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), os.Getenv("SSL_MODE"))

    // Open a connection to the database using GORM and PostgreSQL driver.
    db, err := gorm.Open(postgres.New(postgres.Config{
        DSN:                  configurations,
        PreferSimpleProtocol: true,
    }), &gorm.Config{NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    }})
    if err != nil {
        return nil, err
    }

    // Enable connection pooling by configuring maximum idle and open connections.
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)

    // Return the Database instance with the established database connection.
    return &Database{
        Client: db,
    }, nil
}

You can declare a Run function that instantiates the startup of your app in the main.go file and then call the function in the main function.

// internal/models/migrations.go

func (d *Database) MigrateDB() error {
    log.Println("Database Migration in Process...")

    // Use GORM AutoMigrate to migrate all the database schemas.
    err := d.Client.AutoMigrate(&User{})
    if err != nil {
        return err
    }

    log.Println("Database Migration Complete!")
    return nil
}

The Run function creates a database instance, initializes migrations functionality, initializes the HTTP and User implementations and starts the server.

You can call the Run function in the main function to launch your application.

// internal/models/users.go

func (d *Database) CreateUser(ctx context.Context, user *users.User) error {
    newUser := &User{
        Username: user.Username,
        Email:    user.Email,
        IsActive: false,
    }

    if err := d.Client.WithContext(ctx).Create(newUser).Error; err != nil {
        return err
    }

    return nil
}

// GetUserByID returns the user with a specified id
func (d *Database) GetUserByID(ctx context.Context, id int64) (users.User, error) {
    user := users.User{}
    if err := d.Client.WithContext(ctx).Where("id = ?", id).First(&user).Error; err != nil {
        return users.User(User{}), err
    }
    return users.User(User{
        Username: user.Username,
        Email:    user.Email,
        IsActive: user.IsActive,
    }), nil
}

// UpdateUser updates an existing user in the database
func (d *Database) UpdateUser(ctx context.Context, updatedUser users.User, id uint) error {
    // Check if the user with the specified ID exists
    var existingUser User
    if err := d.Client.WithContext(ctx).Where("id = ?", id).First(&existingUser).Error; err != nil {
        return err
    }

    // Update the fields of the existing user with the new values
    existingUser.Username = updatedUser.Username
    existingUser.Email = updatedUser.Email
    existingUser.IsActive = updatedUser.IsActive

    // Save the updated user back to the database
    if err := d.Client.WithContext(ctx).Save(&existingUser).Error; err != nil {
        return err
    }

    return nil
}

// DeleteUser deletes a user from the database by their ID

func (d *Database) DeleteUser(ctx context.Context, id uint) error {
    // Check if the user with the specified ID exists
    var existingUser User
    if err := d.Client.WithContext(ctx).Where("id = ?", id).First(&existingUser).Error; err != nil {
        return err
    }

    // Delete the user from the database
    if err := d.Client.WithContext(ctx).Delete(&existingUser).Error; err != nil {
        return err
    }

    return nil
}

The application should run fine before you consider containerizing it with Docker.

Step3: Writing the Dockerfile

Now that you’ve successfully built and ran the program, you can proceed to containerize it with Docker.

Your Dockerfile will have two stages, the build and final stage. This approach reduces image size, minimizes security risks by reducing the attack surface, ensures efficient runtime performance, and facilitates reproducibility across different development and deployment stages.

You’ll also use Alpine Linux as the base image of your Docker images since they’re more efficient and secure with minimalist design results in smaller image sizes, faster builds, and reduced attack surfaces.

Step 3a: The Build Stage

Using the build and final stages in a Dockerfile allows for the efficient creation of Docker images. The build stage starts with a base image containing build tools and dependencies, compiles application artifacts, and generates a potentially large intermediate image.

Here’s the contents of the Dockerfile for the Build Stage:

go mod init
  1. FROM golang:1.20-alpine AS build: This line specifies the base image for the build stage. It starts with the official Golang Docker image tagged with version 1.20 and is based on Alpine Linux. The AS build part gives this stage a name, "build" that you can reference later.
  2. WORKDIR /app: This line sets the working directory inside the container to /app. Docker will execute subsequent commands in this directory.
  3. COPY . .: This command copies the contents of the current directory (presumably your Go application source code and other necessary files) into the /app directory inside the container.
  4. RUN go build -o server ./cmd/server: This is the command that builds the Go application. It uses the go build command to compile the Go code in the current directory and output the binary as server. The ./cmd/server argument is the location of the application code relative to the /app directory.

Step 3b: The Final Stage

The final stage employs a smaller base image, copies only necessary runtime components, and results in a compact image optimized for production.

Here are the contents of your Dockerfile for the final stage:

go mod init
  1. FROM alpine:latest: In the final stage, you can start with an Alpine Linux base image, The latest tag specifies the latest available version of Alpine Linux.
  2. WORKDIR /app: This line sets the working directory inside the container to /app. Docker will execute subsequent commands in this directory.
  3. COPY --from=build /app/server .: This command copies the binary file named server from the previous "build stage" into the /app directory inside the final container. This binary is the compiled Go application that you built in the build stage.
  4. EXPOSE 8080: Here, you specify that your application will listen on port 8080. This is a declaration and does not actually open the port; it's a way to document which port your application expects to use.
  5. CMD ["./server"]: This command will be executed when you run a container based on the image. It specifies running the server binary, which is your Go application. This command starts your application inside the container.

Step 4: Building and Running the Docker Image

After Writing the Dockerfile, you can proceed to build and run the file.
Run this command to build the Docker image from the file with the build command.

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

The -t flag specifies the tag for the Docker image as betterapp and the following dot (.) specifies that you want to build the Dockerfile in the current directory.

You can run the image with the run command and specify a port mapping from the container to your host machine with the -p flag.

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

The subsequent -e flags are for specifying environment variables since for your application.

Step 5: Deploying Go Applications with Docker

Docker Compose is a container orchestration tool that simplifies working with multiple Docker containers. You can use Docker compose to orchestrate your Go apps and their components.

You’ll use a YAML file to specify the instruction and Docker compose will setup your applications to save you time and complexity.

First, create a Docker Compose YAML file with the command below and open the file in your editor:

package models

import (
    // imports from the user implementation
    "BetterApp/internal/users"

    "context"
    "gorm.io/gorm"
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm/schema"
    "os"
)

After creating the Dockerfile, you can start writing the commands and directives for deploying your app:

// internal/models/users.go

type User struct {
    gorm.Model
    Username string `gorm:"unique;not null"`
    Email    string `gorm:"unique;not null"`
    IsActive bool   `gorm:"not null"`
}

The YAML file defines two services: my-postgres which is the database container instance and the web service, which is your Go application before configuring their environment variables, ports, and dependencies.

Now, you can proceed to build the images with the docker-compose build command.

// internal/models/database.go

type Database struct {
    Client *gorm.DB
}

Your output should be similar to this:

How to Deploy Go Applications With Docker

Finally, you can run your containers with the docker-compose up command.

go mod init

The -d flag runs the containers in detached mode which makes it agnostic of the terminal session.

Here’s the result of running the command:

How to Deploy Go Applications With Docker

You can close your terminal, and the container should continue running.

You can run the CURL requests to test your API once the containers are up:

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

Congratulations, you’ve successfully deployed and run a working Go app with Docker and Docker Compose.

Conclusion

You’ve learned how to build and simplify your Go app’s deployment with Docker and Docker Compose. As you continue on your development journey, the skills and understanding you've gained here will prove to be essential assets in ensuring smooth deployments and operational excellence.

Consider exploring advanced Docker features like optimizing Dockerfile builds or implementing Docker Swarm for larger applications.

The above is the detailed content of How to Deploy Go Applications With Docker. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
C   and Golang: When Performance is CrucialC and Golang: When Performance is CrucialApr 13, 2025 am 12:11 AM

C is more suitable for scenarios where direct control of hardware resources and high performance optimization is required, while Golang is more suitable for scenarios where rapid development and high concurrency processing are required. 1.C's advantage lies in its close to hardware characteristics and high optimization capabilities, which are suitable for high-performance needs such as game development. 2.Golang's advantage lies in its concise syntax and natural concurrency support, which is suitable for high concurrency service development.

Golang in Action: Real-World Examples and ApplicationsGolang in Action: Real-World Examples and ApplicationsApr 12, 2025 am 12:11 AM

Golang excels in practical applications and is known for its simplicity, efficiency and concurrency. 1) Concurrent programming is implemented through Goroutines and Channels, 2) Flexible code is written using interfaces and polymorphisms, 3) Simplify network programming with net/http packages, 4) Build efficient concurrent crawlers, 5) Debugging and optimizing through tools and best practices.

Golang: The Go Programming Language ExplainedGolang: The Go Programming Language ExplainedApr 10, 2025 am 11:18 AM

The core features of Go include garbage collection, static linking and concurrency support. 1. The concurrency model of Go language realizes efficient concurrent programming through goroutine and channel. 2. Interfaces and polymorphisms are implemented through interface methods, so that different types can be processed in a unified manner. 3. The basic usage demonstrates the efficiency of function definition and call. 4. In advanced usage, slices provide powerful functions of dynamic resizing. 5. Common errors such as race conditions can be detected and resolved through getest-race. 6. Performance optimization Reuse objects through sync.Pool to reduce garbage collection pressure.

Golang's Purpose: Building Efficient and Scalable SystemsGolang's Purpose: Building Efficient and Scalable SystemsApr 09, 2025 pm 05:17 PM

Go language performs well in building efficient and scalable systems. Its advantages include: 1. High performance: compiled into machine code, fast running speed; 2. Concurrent programming: simplify multitasking through goroutines and channels; 3. Simplicity: concise syntax, reducing learning and maintenance costs; 4. Cross-platform: supports cross-platform compilation, easy deployment.

Why do the results of ORDER BY statements in SQL sorting sometimes seem random?Why do the results of ORDER BY statements in SQL sorting sometimes seem random?Apr 02, 2025 pm 05:24 PM

Confused about the sorting of SQL query results. In the process of learning SQL, you often encounter some confusing problems. Recently, the author is reading "MICK-SQL Basics"...

Is technology stack convergence just a process of technology stack selection?Is technology stack convergence just a process of technology stack selection?Apr 02, 2025 pm 05:21 PM

The relationship between technology stack convergence and technology selection In software development, the selection and management of technology stacks are a very critical issue. Recently, some readers have proposed...

How to use reflection comparison and handle the differences between three structures in Go?How to use reflection comparison and handle the differences between three structures in Go?Apr 02, 2025 pm 05:15 PM

How to compare and handle three structures in Go language. In Go programming, it is sometimes necessary to compare the differences between two structures and apply these differences to the...

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
4 weeks agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

mPDF

mPDF

mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

DVWA

DVWA

Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

SecLists

SecLists

SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.