I’m currently creating a backend API using Java 20, and Springboot. Im kind of a beginner. So Ill explain the structure Im following. Before thinking about dockerizing the application, I ussed a docker-compose file to run a PostgreSQL Database.
docker-compose.yaml
services:
db:
container_name: postgres
image: postgres
environment:
POSTGRES_USER: redacted
POSTGRES_PASSWORD: redacted
PGDATA: /data/postgres
volumes:
- db:/data/postgres
ports:
- "5332:5432"
networks:
- db
restart: unless-stopped
networks:
db:
driver: bridge
volumes:
db:
And here is my application.yaml file as well
server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://db:5332/irrigation
username: redacted
password: redacted
data: classpath:data.sql
jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format-sql: true
show-sql: false
main:
web-application-type: servlet
Initially, i ran docker-compose up -d to start up the postgre container on my machine (Im using a MacBook M1 Pro). Its working perfectly on my local machine.
And here is the structure of my project (if It helps in anyways)
.
├── Dockerfile
├── HELP.md
├── docker-compose.yaml
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── amr
│ │ └── irrigation
│ │ ├── Main.java
│ │ ├── Plot.java
│ │ ├── TimeSlot.java
│ │ ├── controllers
│ │ │ ├── PlotController.java
│ │ │ └── TimeslotController.java
│ │ └── repositories
│ │ ├── PlotRepository.java
│ │ └── TimeslotRepository.java
│ └── resources
│ ├── application.yaml
│ ├── data.sql
│ └── import.sql
└── target
├── classes
│ ├── application.yaml
│ ├── com
│ │ └── amr
│ │ └── irrigation
│ │ ├── Main.class
│ │ ├── Plot.class
│ │ ├── TimeSlot.class
│ │ ├── controllers
│ │ │ ├── PlotController.class
│ │ │ └── TimeslotController.class
│ │ └── repositories
│ │ ├── PlotRepository.class
│ │ └── TimeslotRepository.class
│ ├── data.sql
│ └── import.sql
├── generated-sources
│ └── annotations
├── irrigatiosvc-0.0.1-SNAPSHOT.jar
├── irrigatiosvc-0.0.1-SNAPSHOT.jar.original
├── maven-archiver
│ └── pom.properties
└── maven-status
└── maven-compiler-plugin
└── compile
└── default-compile
├── createdFiles.lst
└── inputFiles.lst
The application runs perfectly. I’ve also used the ./mvnw command to build a jar file for my application and I ran it. It works as planned.
What I actually want to do
Is to have my application as a microservice that runs on its own. To be able to run calls on my APIs using postman on localhost:8080, and to have the database running on port 5332. I also want the container to be self-sufficient, or in other words, have all the dependencies it needs to run on any machine.
EDIT: I also want to run a command that created a database called irrigation. As this is the second thing I did manually. The tables, as I assume would happend, supposedly will be created automatically using the import.sql file I have.
What the problem is
Here is my docker file
FROM eclipse-temurin:20.0.1_9-jdk-alpine
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
EXPOSE 8080
EXPOSE 5332
EXPOSE 5432
COPY src ./src
RUN apk add docker-compose
##CMD ["./mvnw", "spring-boot:run"]
CMD ["./mvnw", "spring-boot:run"]
This builds successfully. However, I get faced with an error
Caused by: org.postgresql.util.PSQLException: Connection to localhost:5332 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
COMPLETE LOG FILE: https://pastebin.com/shhtci33
PS: Im not sure if I should paste the log file here or just link it one pastebin. Please correct me if Im wrong.
I have tried for countless hours, and searched a lot. I still dont know what the problem is. I tried running the docker-compose up -d on the dockerfile initially, however, It just returned docker-compose now found. I’d be very glad if someone with more experience can spot where the error is, and potentially explain to me briefly how to solve it. Thank you for reading my question! 🙂
2
Answers
In application.yaml, jdbc url cannot be localhost:5332 because spring application is running on one container and postgres db is on another container. Instead of localhost, it should point hostname of postgres container.
For example, create a container_name for postgres, create a environment variable and use that in jdbc url.
Refer
https://github.com/wkrzywiec/kanban-board/blob/master/kanban-app/src/main/resources/application.properties
https://github.com/wkrzywiec/kanban-board/blob/master/docker-compose.yml
Your application is trying to connect to a PostgreSQL server running on localhost, but when your application is running inside a Docker container, localhost refers to the container itself, not your machine.
In your Docker Compose file, you’ve correctly set up a PostgreSQL server as a service and named it db. So you should configure your Spring Boot application to connect to this server by using the service name db as the hostname, instead of localhost.
However, when you’re running your application inside a Docker container, you aren’t actually using Docker Compose, which is why your application isn’t able to connect to the database.
You need to modify your docker-compose.yaml file to include the application as a service, alongside the database service, and use Docker Compose to run both services. Here is an example of what that might look like:
You should also modify your application.yaml file to connect to the db host instead of localhost:
Now, you should be able to start your application and database with the docker-compose up command, and your application should be able to connect to the database.
Note: Docker Compose sets up a default network for all services defined in a docker-compose.yaml file and service-to-service communication can be done via service names as hostnames. Hence db is used as the hostname in the JDBC connection string.
Also, keep in mind that Docker Compose starts services in dependency order, which in your case means it starts the database before the application. However, it does not wait for a service to be "ready" (such as a database fully starting up and being ready to accept connections) before starting the next one. To handle this, your application should be able to tolerate the database connection not being immediately available, and should retry the connection if the initial connection attempt fails. This could be handled by Spring Boot’s spring.datasource.initialization-mode property set to always (it is embedded by default).
Last but not least, I removed EXPOSE 5332 and EXPOSE 5432 from your Dockerfile as your app container doesn’t need to expose these ports, and updated the PostgreSQL port in application.yaml to 5432 because inside the network created by Docker Compose, the PostgreSQL container will be listening on its original port (5432), not the mapped one (5332).