Prerequisites
Before getting started, this tutorial assumes that one has at least basic knowledge of Golang, enough at least to read the code. The use of docker compose is to be able to at least add a database and ensure the code can be run on any device without having to have the tools installed on the machine.
The following definition below, got from the docker compose website, gives a rough overview of what it is and why it is advisable to use it.
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration
Installation
You will require to install the following tools:
The Golang application
We will write a simple Golang application web application in order to test out that the connection works since the main point of the blog is to explore docker compose and deploying to production.
First thing first, let's show a simple file structure
1
2
3
|
+-- docker-compose.yml
+-- main.go
+-- Dockerfile
|
Pretty simple right? Let's get started:
We'll create a very simple Golang application that has a hello World route and connects to the database, since the main aim of this tutorial is to dockerize our web application and not to create some fancy web app. For fancier tutorials check back soon on this blog. In the main.go file add this code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
_ "github.com/lib/pq"
)
type Note struct {
Name string
Date time.Time
Description string
}
// Function for initializing a connection to the database
func initializeDB(dbPort int, dbName, dbUser, dbPassword, dbHost string) (*sql.DB, error) {
var err error
dbInformation := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", dbHost, 5432, dbUser, dbPassword, dbName)
db, err := sql.Open("postgres", dbInformation)
if err != nil {
log.Fatal("This is the error: ", err)
fmt.Printf("Cannot connect to %s database", dbInformation)
return nil, err
}
err = db.Ping()
if err != nil {
log.Fatal(err)
return nil, err
}
log.Println("Database Connection established")
return db, nil
}
// Hello world function that returns current time and a text response from the struct created above
func helloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Println("hello world")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
note1 := Note{"Trial", time.Now().Local(), "This is a test"}
data, err := json.Marshal(note1)
if err != nil {
fmt.Println(err)
}
w.Write(data)
}
func main() {
// Initialize database connection with default values (Not for production purposes
database, err := initializeDB(5432, "postgres", "postgres", "postgres", "postgres")
if err != nil {
log.Fatalf("Could not set up database %v", err)
}
// Ensure connection does not close once it has been established
defer database.Close()
// Initialize the http server
http.HandleFunc("/", helloWorld)
log.Fatal(http.ListenAndServe(":8080", nil))
}
|
The DockerFile
Once that is done, we can now finally start the main event, the dockerization. * Cue in drums, trumpets and people singing *. We first need to create a dockerfile for the main app (we could do it all in the docker-compose file but I prefer it this way).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
FROM golang:latest as builder
LABEL maintainer = "Some maintainer <someMaintainer@email.com>"
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Starting a new stage from scratch
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 10000 10000
CMD ["./main"]
|
We have just created a multi-stage docker build for Go which significantly reduces the time it takes to run as well as reduces the size of the final docker image created.
The Docker-compose file
We can now afterwards create the docker-compose file which will hold the database docker container and allow us to run both docker containers simultaneously. In the docker-compose.yml add the following lines of code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
version: "3.7"
services:
server:
build:
dockerfile: Dockerfile
context: .
env_file: .env
depends_on:
- database
networks:
- default
ports:
- "10000:10000"
networks:
- backend
database:
image: postgres
restart: always
env_file:
- .env
ports:
- "9002:5432"
volumes:
- data:/var/lib/postgresql/data
networks:
- backend
volumes:
data:
networks:
backend:
|
Now to put all the pieces together.