Using Docker to Deploy a Containerized Java Web App
Docker can be used to create a familiar environment for devs to work on. Using a simple API, you can build a Docker image and deploy it to suit your needs.
Join the DZone community and get the full member experience.
Join For Freenot 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:
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:
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:
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:
couchbase_host=couchbase
couchbase_bucket=default
couchbase_bucket_password=
in the above snippet, 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:
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:
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:
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:
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 snippet, 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:
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:
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:
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 incredibly convenient for maintenance.
Published at DZone with permission of Nic Raboy, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments