In my development series regarding Docker containers for the web application developer, I had already gone over deploying Java and Node.js applications as containers alongside Couchbase Server Containers. Â This time around I thought it would be cool to deploy a Golang web application as a Docker container alongside Couchbase.
The process is very similar to what I had already demonstrated in the previous tutorials, but it is worth exploring. Â We’re going to explore containerizing an application that I had explained in a previous tutorial on the topic of URL shortening services.
If you’re interested, you can check out Use Docker to Deploy a Containerized Java with Couchbase Web Application and Deploy a Node.js with Couchbase Web Application as Docker Containers, depending on your language preferences.
The Requirements
There is only one dependency that must be met to be successful with this guide. Â You need Docker installed on a host machine. Â You don’t need Golang installed or Couchbase Server since that is the beauty of the Docker Engine.
While you don’t have to, I would recommend becoming familiar with the application in question.  To learn about the code behind the application, check out the tutorial I wrote called, Create a URL Shortener with Golang and Couchbase NoSQL.
Establishing a Docker Project for the Golang Application
The source code to the application, and what we’ll be bundling, can be seen below.  You should save it as a Go source code file such as main.go.
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
package main import ( "encoding/json" "log" "net/http" "os" "time" "github.com/couchbase/gocb" "github.com/gorilla/mux" "github.com/speps/go-hashids" ) type MyUrl struct { ID string `json:"id,omitempty"` LongUrl string `json:"longUrl,omitempty"` ShortUrl string `json:"shortUrl,omitempty"` } var bucket *gocb.Bucket var bucketName string func ExpandEndpoint(w http.ResponseWriter, req *http.Request) { var n1qlParams []interface{} query := gocb.NewN1qlQuery("SELECT `" + bucketName + "`.* FROM `" + bucketName + "` WHERE shortUrl = $1") params := req.URL.Query() n1qlParams = append(n1qlParams, params.Get("shortUrl")) rows, _ := bucket.ExecuteN1qlQuery(query, n1qlParams) var row MyUrl rows.One(&row) json.NewEncoder(w).Encode(row) } func CreateEndpoint(w http.ResponseWriter, req *http.Request) { var url MyUrl _ = json.NewDecoder(req.Body).Decode(&url) var n1qlParams []interface{} n1qlParams = append(n1qlParams, url.LongUrl) query := gocb.NewN1qlQuery("SELECT `" + bucketName + "`.* FROM `" + bucketName + "` WHERE longUrl = $1") rows, err := bucket.ExecuteN1qlQuery(query, n1qlParams) if err != nil { w.WriteHeader(401) w.Write([]byte(err.Error())) return } var row MyUrl rows.One(&row) if row == (MyUrl{}) { hd := hashids.NewData() h := hashids.NewWithData(hd) now := time.Now() url.ID, _ = h.Encode([]int{int(now.Unix())}) url.ShortUrl = "http://localhost:12345/" + url.ID bucket.Insert(url.ID, url, 0) } else { url = row } json.NewEncoder(w).Encode(url) } func RootEndpoint(w http.ResponseWriter, req *http.Request) { params := mux.Vars(req) var url MyUrl bucket.Get(params["id"], &url) http.Redirect(w, req, url.LongUrl, 301) } func main() { router := mux.NewRouter() cluster, _ := gocb.Connect("couchbase://" + os.Getenv("COUCHBASE_HOST")) bucketName = os.Getenv("COUCHBASE_BUCKET") bucket, _ = cluster.OpenBucket(bucketName, "") router.HandleFunc("/{id}", RootEndpoint).Methods("GET") router.HandleFunc("/expand/", ExpandEndpoint).Methods("GET") router.HandleFunc("/create", CreateEndpoint).Methods("PUT") log.Fatal(http.ListenAndServe(":12345", router)) } |
The only difference between the code above and the previous tutorial on the subject is the use of the os.Getenv("COUCHBASE_HOST")
and os.Getenv("COUCHBASE_BUCKET")
commands. Â This will allow us to define the Couchbase connection information at container runtime via environment variables rather than hardcoding it into the application.
If you had Couchbase Server running, you could launch the web application by doing:
1 |
env COUCHBASE_HOST=localhost COUCHBASE_BUCKET=default go run *.go |
Of course the project would need to be in your $GOPATH, but it would be accessible from http://localhost:12345 as defined in the code.
The goal here is not to run this project locally, but via a Docker container.  Create a Dockerfile file next to the main.go file or whatever you called it.  The Dockerfile file should contain the following:
1 2 3 4 5 6 7 8 9 10 11 12 |
FROM golang:alpine RUN apk update && apk add git COPY . /go/src/app/ WORKDIR /go/src/app RUN go get -d -v RUN go install -v CMD ["app"] |
As a base we’ll be using the official Golang Alpine Linux image for Docker, but we’ll be customizing it.  During build time we are installing Git, copying the main.go file to the image, installing the project dependencies found in the import
section, and installing the application. Â At runtime we are executing the installed application.
To build an image from our custom Docker blueprint, we would execute the following:
1 |
docker build -t golang-project . |
After all the compile-time steps complete we’ll be left with a golang-project
Docker image. Â Before we try to run this image, we need to worry about Couchbase. Â While we could run Couchbase outside a container, where is the fun in that?
Creating a Custom Couchbase Server Docker Image
Just like with Golang, an official Docker image exists for Couchbase Server. Â While it is a perfectly acceptable solution, it is not an automated solution. Â This means that you’ll have to manually configure the database after the container starts. Â We probably want to avoid that.
Instead, create a directory somewhere on your computer with a Dockerfile file and configure.sh file in it.  The plan is to create a Docker blueprint that will execute the configuration script for us.
Open the Dockerfile file and include the following:
1 2 3 4 5 |
FROM couchbase COPY configure.sh /opt/couchbase CMD ["/opt/couchbase/configure.sh"] |
We’re basically just copying the script into the base image and running it. Â The real magic happens inside of the script.
Open the configure.sh file and include the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
set -m /entrypoint.sh couchbase-server & sleep 15 curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=512 -d indexMemoryQuota=512 curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2cn1ql%2Cindex curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=$COUCHBASE_ADMINISTRATOR_USERNAME -d password=$COUCHBASE_ADMINISTRATOR_PASSWORD curl -i -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized' curl -v -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/pools/default/buckets -d name=$COUCHBASE_BUCKET -d bucketType=couchbase -d ramQuotaMB=128 -d authType=sasl -d saslPassword=$COUCHBASE_BUCKET_PASSWORD sleep 15 curl -v http://127.0.0.1:8093/query/service -d "statement=CREATE PRIMARY INDEX ON `$COUCHBASE_BUCKET`" fg 1 |
Couchbase Server has a RESTful interface which will allow us to complete the configuration wizard through a script. Â The above commands will define instance specs and services, administrative credentials, and a Bucket.
You’ll notice things like $COUCHBASE_ADMINISTRATOR_USERNAME
within the script. Â Just like in the Golang application we are leveraging environment variables to prevent having to hardcode values into our image. Â These values can be defined during the container deployment.
More information on provisioning a Couchbase Server container can be found in a previous article that I wrote on the subject.
Finally we can build this custom Couchbase Server image. Â From the Docker Shell, execute the following:
1 |
docker build -t couchbase-custom . |
The above command will leave us with a couchbase-custom
image.
Deploying the Containerized Microservice
There are a few ways to deploy the images that we built. Â We’ll explore two of them here.
Given what we know about our images, we can deploy our containers with the following commands:
1 2 3 4 5 6 7 8 9 |
docker run -d \ -p 8091-8093:8091-8093 \ -e COUCHBASE_ADMINISTRATOR_USERNAME=Administrator \ -e COUCHBASE_ADMINISTRATOR_PASSWORD=password \ -e COUCHBASE_BUCKET=default \ -e COUCHBASE_BUCKET_PASSWORD= \ --network="docker_default" \ --name couchbase \ couchbase-custom |
The above command will deploy Couchbase Server while mapping the necessary ports to the host operating system. Â We’re passing each environment variable in with the command and defining an operating network. Â The network is important because you want both your database and application to be on the same container network.
1 2 3 4 5 6 7 |
docker run -d \ -p 12345:12345 \ -e COUCHBASE_HOST=couchbase \ -e COUCHBASE_BUCKET=default \ --network="docker_default" \ --name golang golang-project |
The above command will deploy our Golang image while mapping the correct ports again.  For the host you’ll notice that we’re using couchbase
which is actually the name of our other container. Â The container names also act as host names.
The commands you just saw are a very manual way to do things in my opinion. Â Instead you can create a Docker Compose file to handle this for you.
Create a docker-compose.yml file somewhere on your computer with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
version: '2' services: couchbase: image: couchbase-custom ports: - 8091:8091 - 8092:8092 - 8093:8093 environment: - COUCHBASE_ADMINISTRATOR_USERNAME=Administrator - COUCHBASE_ADMINISTRATOR_PASSWORD=password - COUCHBASE_BUCKET=default - COUCHBASE_BUCKET_PASSWORD= golang: image: golang-project ports: - 12345:12345 environment: - COUCHBASE_HOST=couchbase - COUCHBASE_BUCKET=default restart: always |
Everything in a Compose file will be on the same network. Â To deploy each service, execute the following:
1 2 |
docker-compose run -d --service-ports couchbase docker-compose run -d --service-ports golang |
If you’re familiar with Docker Compose you’ll be familiar with docker-compose up
, but we can’t use that here. Â Couchbase Server doesn’t have a mechanism to tell us it is ready, so we don’t want the Golang application to try to connect before it is configured. Â Definitely not a big deal when it comes to container deployment.
Conclusion
You just saw how to create a Golang microservice as a Docker container that communicates to Couchbase Server, also running within a Docker container. Â By turning your application into a Docker image it becomes very easy to distribute because you won’t have to worry about the environment you’re trying to deploy to. Â As long as the Docker Engine is available you can deploy your application. Â A useful example of this would be in continuous deployment of containers using Jenkins.
Want to see how to containerize your Java application? Â Check out this tutorial I wrote. Â I also have a tutorial for containerizing a Node.js application here.
Want more information on using Golang with Couchbase? Check out the Couchbase Developer Portal for documentation and examples.