Deploy last, prove first
A Spring Boot SCM service that manages procurement and delivery should not reach Docker Hub because “it compiled on my machine”. I wanted a pipeline that fails closed.
The flow I used:
- Run tests.
- Collect coverage with JaCoCo.
- Analyze with SonarQube.
- Build and publish the Docker image only if the gate is green.
GitHub Actions owns that sequence. Docker Compose keeps the backend, database, and SonarQube aligned locally and in CI.
The API itself is described in Securing a Spring Boot SCM API with JWT. This post is the factory around it.
What the workflow actually does
A typical job graph:
- Checkout the repo.
- Start Compose services the tests need.
- Run JUnit 5 tests, including TestContainers cases.
- Publish coverage.
- Wait for SonarQube to accept the quality gate.
- Build the image and push to Docker Hub.
If Sonar fails, the push never happens. That is the whole point.
Compose as the shared environment
Locally I run the same Compose file: API, database, SonarQube. CI should not invent a second topology. When a TestContainers test needs Postgres, the version matches what Compose uses in development.
Why this matters for B2B work
Inventory mistakes are expensive. A flaky migration or an untested security filter is not a “later” problem. The pipeline is how I refuse to ship those.
I also keep secrets out of the repo. Docker Hub credentials and Sonar tokens live in GitHub Actions secrets. The Dockerfile stays small: a production JRE, the built jar, and a non-root user when possible.
What I check before merging
- The Actions run is green on the default branch.
- Coverage did not silently drop on the files I touched.
- The image tag that Docker Hub received matches the commit I expect.
If you want the same kind of delivery discipline on a Java backend, look at my projects or contact me.
More on the test stack: TestContainers, JaCoCo, and SonarQube.
Frequently Asked Questions
What belongs in the quality gate?
Unit tests, integration tests with TestContainers, coverage from JaCoCo, and SonarQube analysis. The image build runs only if those steps pass.
Why Docker Compose in local and CI?
The backend, database, and SonarQube should boot the same way on a laptop and in the pipeline. Compose is that shared recipe.
When do you push to Docker Hub?
After the gate is green. Publishing a broken image trains the team to ignore tags.
Related articles
Securing a Spring Boot SCM API with JWT
A practical look at securing a B2B Spring Boot SCM API with JWT, MVC layers, and a quality gate before every production deploy.
Testing Spring Boot with TestContainers and Sonar
The QA chain I use on Spring Boot backends: JUnit 5, Mockito, TestContainers, JaCoCo coverage, and a SonarQube quality gate.