Dockerizing a React and Spring Boot application

Elvis Ciotti
3 min readJan 29, 2022

--

I’ll explain below in the detail how to create containers (via Dockerfile) for a React and also a Spring Boot application with Postgres (or MySQL). One does not require the other, so you can use this guide to build either one. I’ll also present a docker-compose file useful (but not mandatory) to build and run them

Spring Boot (via Maven) Dockerfile

To minimise sizes, a good approach is a multistage build where stage one starts from maven, then copying the POM file and running clean install to generate JAR files. Stage 2 is starting from a clean JDK image with the JAR being copied, and the entrypoint to launch the JAR file

FROM maven:3.8.4-openjdk-17 AS MAVEN_BUILD
COPY pom.xml /build/
COPY mvnw /build/
COPY .mvn /build/.mvn
COPY src /build/src/
WORKDIR /build/
RUN mvn clean install -Dmaven.test.skip=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -B --no-transfer-progress -e && mvn package -Dmaven.test.skip=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -B --no-transfer-progress -e

FROM openjdk:17-oracle
WORKDIR /app
COPY --from=MAVEN_BUILD /build/target/<name>-<version>.jar /app/flow-api.jar
EXPOSE 5000
ENTRYPOINT [ "java", \
"-Dfile.encoding=UTF-8", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", \
"/app/<name>.jar" \
]

Replace <name> and <version> according to your pom settings.
Don’t worry about passing profile and settings to the command line, this can be done via ENV vars or even entrypoint override via docker-compose later

React Dockerfile (HTML bundle, via yarn)

The idea is building the app from the node container at stage 1, then copying the build (or distdepending on your settings) folder into a nginx container (stage2) where we don’t need node at all.

FROM node:17.4-alpine3.14 as builder
WORKDIR /app
COPY . .
RUN rm -rf node_modules && yarn
RUN yarn run build

FROM nginx:1.21.5-alpine
COPY --chown=nginx:nginx nginx-ui.conf /etc/nginx/conf.d/default.conf
COPY --chown=nginx:nginx --from=builder /app/build /var/www/html/

Note that you also need to copy the nginx config. Place a file like this into the ui root folder (same folder where package.json sits)

# nginx-ui.conf
server {
listen 80;
listen [::]:80;
root /var/www/html/;
index index.html;
# server_name localhost;
# error_page 404 /404.html;
# error_page 500 502 503 504 /50x.html;
location / {
try_files $uri $uri/ /index.html;
}
}

Docker-compose.yaml file

You don’t need a docker-compose file, as on production you’ll probably have a kubernetes setup. However, I need one for local development to launch the database and other services (AWS localstack for S3, redis, ClamAV etc.. ), so that can be used to build and test images working with ENV variables.

The following is a working file for an application composed by React and Spring boot connecting to Postgres

  • UI: the React app. Note that I’m running on port 8080 so you can access via http://localhost:8080. Not a good practice to use port 80 as it might be already taken by another running app (e.g. docker-desktop k8s ingress, other containers or even apps like skype)
  • API: The Spring Boot app. node that I didn’t specify a profile in the entrypoint. That’s wanted as I want it to run on prod mode. In order to use this image in different envs (staging, branches env, prod), it’s better to specify vars via ENV vars. Spring has many options to do that via single ENVS or a single one (I’ll show that case in a future article with a k8s config) . Note that the database DSN host has to match the database container name (that’s how docker-compose works, read more here)
  • POSTGRES/MYSQL: I’m using another port but I can also run on the default one. I just wanted to show that the 5433 is the one used by the api container. The customised command is to log all the queries, so useful when executed locally. Note that you can use this docker-compose file to just launch Postgres when developing locally with docker-compose -d postgres.
services:
ui:
container_name: app_ui
build: app-ui
ports:
- 8080:80

api:
container_name: app_api
build: app-api
environment:
# note that "postgres" has to match the name of the postgres container
SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/<db_name>"
SPRING_DATASOURCE_USERNAME: <db_user>
SPRING_DATASOURCE_PASSWORD: <db_pass>
SPRING_LOGGING_LEVEL_ROOT: INFO
SPRING_SERVER_PORT: 5000
ports:
- 5000:5000

postgres:
container_name: app_postgres
image: postgres:13-alpine
restart: always
command: [ "postgres", "-c", "log_statement=all", "-c", "log_destination=stderr" ]
environment:
POSTGRES_USER: <db_user>
POSTGRES_PASSWORD: <db_pass>
POSTGRES_DB: <db_name>
ports:
- 5433:5432

Follow me for next articles if you are interested. I’ll explain how to build those images with gitlab CI, play with k8s locally natively and with helm, and also deploy with k8s on DigitalOcean

--

--

Elvis Ciotti
Elvis Ciotti

Written by Elvis Ciotti

Software Contractor — Java, Spring, k8s, AWS, Javascript @ London - hire me at https://elvisciotti.github.io/

No responses yet