Simplify Local Development with Docker Compose – Part I
In this blog post, I will explain the ways to setup local development environment using docker compose and automate the process using batch or shell scripts.
Nowadays we are deploying applications in production using docker containers. We can create similar environment in our local development and test our application before committing the code.
This approach will help us to test similar to production and helps us to avoid silly mistakes.
I am going to demonstrate the the approach using the Spring Boot application. But approach can be used by any container based applications.
Creating Dockerfile
Let’s first create Dockerfile in root of the project to create image of our Spring Boot application.
FROM eclipse-temurin:17.0.4_8-jdk-jammy
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN addgroup springboot && adduser sbuser
RUN usermod -G springboot sbuser
USER sbuser
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
Code language: Java (java)
To create Docker image and run application, first we need to package the application to jar and run few docker commands to create image and run application.
Instead of running all the repetitive steps again and again using docker compose file and batch or shell script we can simplify the whole image creation and container running process.
Let create folder local-dev and put our docker-compose and batch/script files in it.
Creating Docker Compose File
version: '3'
services:
springboot-crud-example:
build:
context: ../
dockerfile: ./Dockerfile
ports:
- 8080:8080
- 8000:8000
environment:
JAVA_OPTS:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
Code language: Java (java)
In our docker compose file , we are going to use the Docker file created in step 1.
When ever you want to start and test the application, simply you can run below commands
mvn clean package
Code language: Java (java)
Code language: Java (java)docker-compose up --build --force-recreate
If you want to stop the application, you can run below command.
Code language: Java (java)docker-compose down
We can further simplify the process by creating batch file or shell script by combining the commands.
Batch file
call mvn -f ../pom.xml clean package -DskipTests
echo Exit Code = %ERRORLEVEL%
if not "%ERRORLEVEL%" == "0" (
ECHO "Error building application"
) ELSE (
docker-compose rm -f
docker-compose up --build --force-recreate
)
exit /b
Code language: Java (java)
Shell Script
#!/bin/bash
mvn -f ../pom.xml clean package
rc=$?
if [ $rc -ne 0 ] ; then
echo Errors found during mvn clean verify, exit code [$rc];
else
docker-compose rm -f
docker-compose up --build --force-recreate
fi
Code language: Java (java)
Whenever user wants to start the application, he can simply run batch file or shell script.
rm -f – remove the any running containers
–build – Build images before starting containers
–force-recreate – Recreate containers even if their configuration and image haven’t changed.
force-recreate option will force the docker to create new image every time. As docker caches images without this option, it will use cached image.
Using Local Database
When you are testing containerized application and the application uses database to store and retrieve application, you may want to create a local database and test against it.
your application will not able to connect to locally installed database using host name as docker container runs in separate network
To overcome this problem, In Windows or Mac , you need to change the hostname from localhost to host.docker.internal in your application.properties file
from
spring.datasource.url=jdbc:postgresql://localhost:5432/eis
Code language: Java (java)
to
spring.datasource.url=jdbc:postgresql://host.docker.internal:5432/eis
Code language: Java (java)
In Linux , you can set network mode to host to connect to locally installed database.
version: '3'
services:
springboot-crud-example:
build:
context: ../
dockerfile: ./Dockerfile
ports:
- 8080:8080
- 8000:8000
environment:
JAVA_OPTS:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
network_mode: "host" # allows application to access local database in Linux environment
Code language: Java (java)
Note
In general locally installed databases are setup to accept connections from local systems only. Since docker runs in separate network, applications running in docker won’t able to reach the local database. You can configure the database to accept connections from any network but it poses security risk.
dev and test databases are configured to accept the request from company network , so if your application is pointing to them, you may not need to change the host name in real world applications.
Using Containerized Database
Instead of using local database, you can also use containerized database to test application which requires database access.
We can run the application and database in single network using docker compose file.
version: '3'
services:
springboot-crud-example:
container_name: 'springboot-app'
build:
context: ../
dockerfile: ./Dockerfile
ports:
- 8080:8080
- 8000:8000
environment:
SPRING_PROFILES_ACTIVE: container
JAVA_OPTS:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
depends_on:
- "postgres"
postgres:
container_name: 'postgresdb'
image: 'postgres:13.2'
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: admin
POSTGRES_DB: eis
Code language: Java (java)
In above compose file we are starting application and postgresql database.
To access the database running with in container, we can not use localhost.
There are different approaches we can take so that application can connect to database running within the container.
Using Container Name
We can define name for container and use it as host name. In above compose file we named postgres service as “postgresdb” so we can use it as host name.
from
spring.datasource.url=jdbc:postgresql://localhost:5432/eis
to
spring.datasource.url=jdbc:postgresql://postgresdb:5432/eis
Changing the url in default application.properties file in real world application for testing becomes messy . We can use spring profile concept to create separate profile for container based testing.
Create application-container.properties file in resources folder and place the following property.
spring.datasource.url=jdbc:postgresql://postgresdb:5432/eis
Using Network Alias
We can create a network and assign a alias for for postgres and use it as host
version: '3'
services:
springboot-crud-example:
build:
context: ../
dockerfile: ./Dockerfile
ports:
- 8080:8080
- 8000:8000
environment:
SPRING_PROFILES_ACTIVE: container
JAVA_OPTS:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
depends_on:
- "postgres"
networks:
app-net:
aliases:
- springbootapp
postgres:
image: 'postgres:13.2'
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: admin
POSTGRES_DB: eis
networks:
app-net:
aliases:
- postgresdb
networks:
app-net: {}
Code language: Java (java)
In application-container.properties file place the following property.
spring.datasource.url=jdbc:postgresql://postgresdb:5432/eis
Using Network IP address
We can assign pre-defined IP address to each container and use it as host.
version: '3'
services:
springboot-crud-example:
build:
context: ../
dockerfile: ./Dockerfile
ports:
- 8080:8080
- 8000:8000
environment:
SPRING_PROFILES_ACTIVE: container
JAVA_OPTS:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
depends_on:
- "postgres"
networks:
app-net:
aliases:
- springbootapp
ipv4_address: 172.19.1.2
postgres:
image: 'postgres:13.2'
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: admin
POSTGRES_DB: eis
networks:
app-net:
aliases:
- postgresdb
ipv4_address: 172.19.1.3
networks:
app-net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.19.1.0/24
gateway: 172.19.1.1
Code language: Java (java)
In application-container.properties file place the following property.
spring.datasource.url=jdbc:postgresql://172.19.1.3:5432/eis
Note
Depending on postgressql settings , you may need to override the user and password property also in your profile specific properties file.