Dockerize Spring Boot REST Service with Postgresql on Docker

Docker
I have a very basic understanding of what docker is. My knowledge is based mainly on a couple of YT videos and a few articles. I never had an opportunity to use it. So today I decided to at least try to make my hands dirty.
Steps
I plan to do a few things in following order:
- develop an app: develop a very basic Spring Boot application (REST web sevice) that talks to a database (postgresql)
- install docker on my system
- use external image: try to somehow use an image of postgresql so that when it is running, my app can use it
- create an image with my app
- make them communicate: I expect my app in a container would communicate with a container containing database server
- use my app from localhost when both containers are started
Develop
This one is easy. My REST App has following endpoints:
method | uri | curl for testing |
---|---|---|
GET | /quote/ | curl ’localhost:8080/quote/' |
GET | /quote/1 | curl ’localhost:8080/quote/1' |
POST | /quote | curl -XPOST -H"Content-type:application/json" -d’{“author”:“Kamila”, “text”: “Have fun in life with simple things”}’ ’localhost:8080/quote/' |
DELETE | /quote/1 | curl -XDELETE ’localhost:8080/quote/17' |
GET | /quote/1/thoughts/ | curl ’localhost:8080/quote/1/thoughts/' |
GET | /thought/ | curl ’localhost:8080/thought/' |
GET | /thought/5 | curl ’localhost:8080/thought/5' |
POST | /thought/ | curl -XPOST -H"Content-type:application/json" -d’{“quoteId”:“2”, “text”: “Hard to find balance”}’ ’localhost:8080/thought' |
Did I forget about DELETE /thought/? Yes, I did.
Install docker
I thought I would just
|
|
but on official Docker page there are some more complex install instrucitons for Ubuntu. I was astonished how well things went with installing using apt repository and I could run sample image:
|
|
I only had some minor doubts when I saw the size of all required packages:
|
|
432MB ?
So I am more than happy that docker webpage also has clear instructions on how to uninstall docker-related packages and clean up the system…
Use external image
When I read this basic Baeldung tutorial on Spring Boot with Docker I start to undrestand that I just need to use docker compose and it would start up two containers: my application container and postgresql container. I won’t even have to download any image (at least not explicitely).
Directory structure
Here is my directory structure:
|
|
Here’s my Dockerfile
:
|
|
Here’s my docker-compose.yml
:
|
|
Now let’s docker-compose up
!
|
|
Oh, ARG JAR_FILE points to wrong path. So I manually copy the jar to docker
directory.
By default postgres will use user name as the name of newly created database, so in order to change the default I use POSTGRES_DB
env variable and use stoic_db
as name (so that it is the same name as it is in the definition of SPRING_DATASOURCE_URL
variable).
Both images start now as I do
|
|
Unfortunately, my Spring app complains that the database does not exist. Shouldn’t the database be created automatically as promised in postgresql image documentation ?
Before I check this problem, there is one thing I need to do: I need to add myself (i.e. me as linux user) to docker
group so that I don’t have to sudo
each docker command.
Add my user to docker group
This is actually explained in linux postinstall steps which I completely missed, so let’s fix this:
|
|
Logout/login sequence is not enough for the change to actually be applied, but this one command activates the changes nicely:
|
|
which I check by running docker run hello-world
and it works - no sudo needed.
Analyze problems with my app
The problem is that the app could not connect to stoic_db database. It also complains about not having hibernate.dialect set. So I set the property in application.properties
and enable creation of tables:
|
|
but the app doesn’t start properly anyway. I look around carefully… at docker-compose.yml
- and the enlightment comes instantly. Can you spot the typo I’ve made?
Yes, I misspelled database name… After fixing it everything seems fine.
How can I access my app from my host system?
I think it has something to do with port mapping. I haven’t used any networking options neither in Dockerfile
nor in docker-compose.yml
. I have, however, started reading networking tutorial and found out that I can:
- list networks with
docker network ls
:1 2 3 4 5 6
[karma@tpd|~] docker network ls NETWORK ID NAME DRIVER SCOPE a38f71bf264b bridge bridge local 4e98bd840f8e docker_default bridge local 0e828702870c host host local 91ef5856ce09 none null local
- inspect networks and find out what networks are running containers connected to:
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
[karma@tpd|~] docker network inspect docker_default [ { "Name": "docker_default", "Id": "4e98bd840f8e4da8299a4fe0a21b2ad3ba76726760637f3f5571ffcb48887de6", "Created": "2022-04-28T19:32:05.703472022+02:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "34bb88fe363df182fdc6c99722827aec619eabaebf708c9c23558771b65fcff5": { "Name": "boot-stoic-docker-app", "EndpointID": "8bff4b759ec2d45513f9534828c5f1200627fc559ca3e915adc2e37aab15e8d2", "MacAddress": "02:42:ac:12:00:03", "IPv4Address": "172.18.0.3/16", "IPv6Address": "" }, "fe6120c2c4bb7efd3604a02660ed84743c6b06f5dc1d159c0c57906ff1476409": { "Name": "db", "EndpointID": "03b2775aecf0996e7e1f685e9b0a3d0e7467f727f8250305004033dc9ff63d74", "MacAddress": "02:42:ac:12:00:02", "IPv4Address": "172.18.0.2/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "default", "com.docker.compose.project": "docker", "com.docker.compose.version": "2.3.3" } } ]
So I can see that both containers: db and boot-stoic-docker-app are using docker_network network.
Now I understand that:
- this network (docker_network) is a default network created by
docker compose
(as described in this compose networking tutorial - it is named after the directory in which I run
docker compose
which isdocker
(if I were inapp
directory,docker compose
would create a network namedapp_network
) - such user-created networks allow containers inside it not only use IP addresses (db container has IP address 172.18.0.2 and boot-stoic-docker-app has IP address 172.18.0.3), but also can connect between each-other using container name
- this explains why the value of SPRING_DATASOURCE_URL in
docker-compose.yml
usesdb
- it directly refers to the name of the container
- this explains why the value of SPRING_DATASOURCE_URL in
So, two containers talk to each other using docker_network. But this network is internal - I cannot use it in my host system. I need the boot-stoic-docker-app
to “expose” its 8080 port (which is default port on which Spring Boot listens to http connections - to - let’s say - 8081 port on my local host (it could be the same port, but I want to make sure I understand the order of ports in “colon port mapping notation”).
PORTS convention is: local_port:container_port
To do that I need to modify my compose file and define port mapping.
Defining ports
The change to my service definition in docker-compose
is simple, here’s the relevant fragment (I added two last lines):
|
|
Starting both services (app and db) is successful. I can now connect to web app from local host, however, it seems that the tables are not creeated: here is GET error:
|
|
So, almost done. The app is responding to queries.
Solve db issue
Core exception from the spring boot logs:
Caused by: org.postgresql.util.PSQLException: ERROR: relation "quote" does not exist
and I have just no idea why my spring.jpa.hibernate.ddl-auto:create
is not working.
I try a few things:
- rebuild the application (perhaps my jar is old?)
- use the property of ddl-auto in
docker-compose.yml
, not only inapplication.properties
(perhaps properties file is not taken into account?) - remove conditional creation of CommandlineRunner and add some entites on my boot’s service @PostConstruct (perhaps conditions are not being evaluated correctly)?
Nothing helps.
At some point I start to modify Dockerfile and instead of FROM opejdk:17-alpine
I use FROM openjdk:latest
and things are getting weird: I start getting:
|
|
in the logs and the app just shuts down.
Containers cleanup
The number of containers and images grows. I’m not sure what is necessary and what is not. I also start realize (but not understand) that perhaps those layers, intermediate images or containers, container-image references and caching mechanisms make the state of the system invalid: I probably use od image or try to start (instead of rebuild) the container pointing to invaid image.
I see no other way to do some cleanup:
- get rid of all containers
- get rid of all images (except for postgres:13.1-alpine which I’m using in my
docker-compose
) - I change the Dockerfile to use
FROM openjdk:latest
Then I just docker compose up
and… everything just works. 😄
And it even works if I start and stop the container with docker container stop boot-stoic-docker-app
and docker container start boot-stoic-docker-app
.
Update: 2022-05-06
I have learned two things today:
- docker compose up documentation states two interesting options: –build and –force-recreate. In order for the docker to pick-up changes in my Dockerfile it would be sufficient to just –force-recreate. However, changes in Java app require re-building of the image so I could have just run
docker compose up --build
. maven-resource-plugin
is very handy as it can copy the jar automatically to the location specified by mydockerDicrectory
maven property (here is the relevant stack overflow question with answers that suggested this solution):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
diff --git a/stoic/pom.xml b/stoic/pom.xml index 450ff88..f6ab5dd 100644 --- a/stoic/pom.xml +++ b/stoic/pom.xml @@ -15,6 +15,7 @@ <description>Web service for stoic meditation practice</description> <properties> <java.version>17</java.version> + <dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory> </properties> <dependencies> <dependency> @@ -44,8 +45,37 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> + <!-- copy resulting jar file to /src/main/docker --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.2.0</version> + <executions> + <execution> + <id>copy-resources</id> + <!-- here the phase you need --> + <phase>validate</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${dockerDirectory}</outputDirectory> + <resources> + <resource> + <directory>${project.build.directory}</directory> + <includes> + <include>*.jar</include> + </includes> + </resource> + </resources> + </configuration> + </execution> + + </executions> + </plugin> </plugins> </build> + <repositories> <repository> <id>spring-milestones</id>
Conclusion
So I did it :) And I had some fun!
This excercise was just an apptizer 🍰. Now it is time to read and understand what I actually did. So my path forward would be:
- skim-read a couple of introductory articles (“docker for beginners”)
- read in-depth concepts on docker website
- read about Linux containers (or containers in general) and how they are used by software like Docker
- how Docker compares to systems like Snap (which I use on ubuntu) or flatpak (which I use on debian)
- what it takes to implement Docker yourself?
Resources
Docker related
- Docker commandline reference: https://docs.docker.com/engine/reference/commandline/docker
- post body with curl (and other curl-related commands):https://reqbin.com/req/c-d2nzjn3z/curl-post-body
- first tutorial (seem too strange) https://www.baeldung.com/spring-boot-postgresql-docker
- another one, seems simple: https://www.baeldung.com/dockerizing-spring-boot-application
- tutorial on docker site: https://docs.docker.com/language/java/build-images/
Unrelated, but helpful
- https://www.baeldung.com/maven-copy-files
- preparing data in Spring for integration tests https://www.baeldung.com/spring-boot-data-sql-and-schema-sql
Ten wpis jest częścią serii docker.
- 2022-21-09 - Dockerize Spring Boot App with Frontend in React
- 2022-15-09 - Docker Volumes
- 2022-28-04 - Dockerize Spring Boot REST Service with Postgresql on Docker