Not too long ago I wrote about containerizing a Node.js RESTful API and Couchbase Server to demonstrate how easy it is to deploy web applications in a quick and reliable fashion. Â In that guide we created a simple API, built a Docker image from it, deployed it as a container, and deployed Couchbase as a container. Â However, I understand that not everyone is familiar with Node.js.
Here we’re going to build a simple Java RESTful API using Spring Boot, create a Docker image from it, and deploy it as a container with Couchbase. Â This will create a familiar environment for Java developers.
This tutorial requires that you have a Docker installed and configured on your machine. Â With Docker we’ll be creating custom Docker images and deploying them as containers.
Create a Custom Docker Image for Couchbase Server
Let’s start with creating a custom Docker image for Couchbase Server. Â While an official Couchbase image exists, it isn’t automatically provisioned when deployed. Â Our custom image will automatically provision itself upon deployment as a container.
Somewhere on your computer create a directory with a Dockerfile file and configure.sh file in it.  The Dockerfile file will be the blueprint for our image and the configure.sh file will be the provisioning script that is run when the container is deployed.
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 can be configured through HTTP after being deployed.  Our configuration script will specify instance resources, administrative credentials, a Bucket, and a primary index.  You’ll notice that a variety of variables are used such as $COUCHBASE_ADMINISTRATIVE_USERNAME
and $COUCHBASE_BUCKET
. Â These can be passed in at runtime preventing us from having to hard-code sensitive information.
More information on provisioning a Couchbase container via HTTP can be seen in a previous article that I wrote on the topic.
With the provisioning script complete, we have to finish the Dockerfile file.  Open it and include the following:
1 2 3 4 5 |
FROM couchbase COPY configure.sh /opt/couchbase CMD ["/opt/couchbase/configure.sh"] |
The custom Docker image will use the official Docker image as the base, copy our provisioning script during the build process, and execute it at runtime.
To build the custom image for Couchbase, execute the following:
1 |
docker build -t couchbase-custom /path/to/directory/with/dockerfile |
In the above command couchbase-custom
is the image name and it is built from the path that contains the Dockerfile file.
Developing a Spring Boot RESTful API with Java
Before we can containerize our Java application we have to build it. Â Because we are using Spring Boot, we need to download a starter project. Â This can easily be done from the Spring Initializr website.
For this project I’m using com.couchbase
as my group and docker
as my artifact.  I also prefer Gradle, so I’m using that instead of Maven.
Extract the downloaded project, and open the project’s src/main/resources/application.properties file.  In this file include the following:
1 2 3 |
couchbase_host=couchbase couchbase_bucket=default couchbase_bucket_password= |
In the above we are assuming our host instance is called couchbase
and it has a passwordless Bucket called default
. Â If you were testing locally, the host would probably be localhost instead. Â In any case, all these properties are going to be defined at container runtime through environment variables.
Now open the project’s src/main/java/com/couchbase/DockerApplication.java file.  Here we’re going to load our properties and define our endpoints.  Open this file and include the following Java 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 64 65 66 67 68 69 70 71 72 73 74 |
package com.couchbase; import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.CouchbaseCluster; import com.couchbase.client.java.query.*; import com.couchbase.client.java.query.consistency.ScanConsistency; import com.couchbase.client.java.document.json.JsonObject; import com.couchbase.client.java.document.JsonDocument; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.*; import org.springframework.context.annotation.*; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import java.util.*; import java.util.concurrent.TimeUnit; @SpringBootApplication @RestController @RequestMapping("/") public class DockerApplication { @Value("${couchbase_host}") private String hostname; @Value("${couchbase_bucket}") private String bucket; @Value("${couchbase_bucket_password}") private String password; public @Bean Cluster cluster() { return CouchbaseCluster.create(hostname); } public @Bean Bucket bucket() { return cluster().openBucket(bucket, password); } @RequestMapping(value="/", method= RequestMethod.GET) public String root() { return "Try visiting the `/get` or `/save` endpoints"; } @RequestMapping(value="/get", method= RequestMethod.GET) public Object get() { String query = "SELECT `" + bucket().name() + "`.* FROM `" + bucket().name() + "`"; return bucket().async().query(N1qlQuery.simple(query, N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS))) .flatMap(AsyncN1qlQueryResult::rows) .map(result -> result.value().toMap()) .toList() .timeout(10, TimeUnit.SECONDS) .toBlocking() .single(); } @RequestMapping(value="/save", method=RequestMethod.POST) public Object save(@RequestBody String json) { JsonObject jsonData = JsonObject.fromJson(json); JsonDocument document = JsonDocument.create(UUID.randomUUID().toString(), jsonData); bucket().insert(document); return new ResponseEntity<String>(json, HttpStatus.OK); } public static void main(String[] args) { SpringApplication.run(DockerApplication.class, args); } } |
Not too much is happening in the above. Â Much of it is boilerplate code and import statements. Â Because the goal of this article isn’t in regards to using Java with Couchbase, I won’t explain each part of the code. Â Instead know that it has three endpoints, one of which will get all documents in the Bucket and one of which will save new documents to Couchbase.
If you’re using Gradle like I am, you need to change the build.gradle file.  It needs to have a task created and dependencies added.  Your build.gradle file should look something like this:
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 |
buildscript { ext { springBootVersion = '1.5.2.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.springframework:spring-tx') compile('org.springframework.security:spring-security-core') compile('com.couchbase.client:java-client') testCompile('org.springframework.boot:spring-boot-starter-test') } task(run, dependsOn: 'classes', type: JavaExec) { main = 'com.couchbase.DockerApplication' classpath = sourceSets.main.runtimeClasspath } |
To build the application, execute the following:
1 |
gradle build -x test |
Now you’ll have a JAR file to be used in our Docker image.
Build a Custom Docker Image for the Spring Boot Application
Building a custom image will require that we have a Dockerfile file in place.  At the base of your Java project, add a Dockerfile file and include the following:
1 2 3 4 5 |
FROM openjdk:8 COPY ./build/libs/java-project-0.0.1-SNAPSHOT.jar spring-boot.jar CMD java -jar spring-boot.jar |
In the above we’re using the official OpenJDK image as our base and we’re copying our JAR into the image at build time. Â At deployment, the JAR is executed.
To build this image, execute the following:
1 |
docker build -t spring-boot-custom /path/to/directory/with/dockerfile |
The above command should look familiar.  We’re creating a spring-boot-custom
image using the blueprint found in the directory of our Dockerfile file.
For more information on creating custom Docker images, you can visit a previous article I wrote called, Build a Custom Docker Image for Your Containerized Web Application.
Deploying the Couchbase and the Spring Boot Images as Containers
There are a few options when it comes to deploying our images. Â We can use a Compose file or deploy them as vanilla containers. Â I find Compose to be a cleaner approach so we’ll use that.
Somewhere on your computer create a docker-compose.yml 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 22 23 24 |
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= spring-boot: image: spring-boot-custom ports: - 8080:8080 environment: - COUCHBASE_HOST=couchbase - COUCHBASE_BUCKET=default - COUCHBASE_BUCKET_PASSWORD= restart: always |
In the above file we are defining the custom images that we built and we are doing port mapping to the host machine.  What is particularly interesting is the environment
options.  These match the variables that we have in our application.properties and configure.sh files.
To deploy our containers with Compose, execute the following:
1 2 |
docker-compose run -d --service-ports --name couchbase couchbase docker-compose run -d --service-ports --name spring-boot spring-boot |
Something to note about the above commands. Â Couchbase does not deploy instantly. Â You’ll need to wait until it is completely launched before you deploy the Java application. Â After both applications are launched, check them out by navigating to the Java application in your web browser.
Conclusion
You just saw how to create custom Docker images for a Spring Boot application and Couchbase Server. Â After deploying each as containers they will be able to communicate to each other which is incredible convenient for maintenance.
If you’re interested in seeing this done with Node.js, check out the previous article I wrote on the topic. Â If you’re interested in learning more about the Java SDK for Couchbase, check out the Couchbase Developer Portal.
[…] 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 […]