Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Deploy a Golang Web Application and Couchbase as Docker Containers

DZone's Guide to

Deploy a Golang Web Application and Couchbase as Docker Containers

Learn how to deploy a Golang web application as a Docker container alongside Couchbase using microservices and Docker images.

· Database Zone
Free Resource

Learn how to move from MongoDB to Couchbase Server for consistent high performance in distributed environments at any scale.

In my development series regarding Docker containers for the web application developer, I went 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 have already demonstrated in 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 the articles 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 — 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.

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:

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:

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:

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:

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:

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 that 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:

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:

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.

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:

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:

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 the 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 the 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 to deliver a whole new level of customer experience? Learn how to make your move from MongoDB to Couchbase Server.

Topics:
docker ,couchbase ,golang ,database ,web app ,containers ,microservices

Published at DZone with permission of Nic Raboy, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}