End-to-end CI/CD pipeline for a Java/Maven application, implemented entirely with GitHub Actions. On every push to main the workflow auto-bumps the Maven patch version, builds the project, packages it into a multi-stage Docker image, pushes the image to Docker Hub, and deploys it onto an EC2 host over SSH.
push to main
│
▼
┌──────────────────┐
│ Checkout repo │
└────────┬─────────┘
▼
┌──────────────────┐
│ Docker Hub login │
└────────┬─────────┘
▼
┌──────────────────┐
│ Set up Java 11 │
└────────┬─────────┘
▼
┌──────────────────────────┐
│ Bump Maven patch version │ ← auto-increments e.g. 1.0.14 → 1.0.15
└────────┬─────────────────┘
▼
┌────────────────────────────────────────┐
│ Build multi-stage Docker image │
│ stage 1: Maven build → app.jar │
│ stage 2: openjdk:11-jre-slim runtime │
└────────┬────────────────────────────────┘
▼
┌──────────────────────────────────┐
│ Push image to Docker Hub │
└────────┬──────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ Deploy to EC2 over SSH │
│ scp script.sh → host │
│ ssh host → docker pull, stop/rm old, run new │
└──────────────────────────────────────────────────┘
GitHub Actions · Java 11 · Maven · Docker (multi-stage) · Docker Hub · AWS EC2 · SSH
.
├── .github/
│ ├── dependabot.yml # Weekly Maven dependency updates
│ └── workflows/
│ └── ci-cd.yml # The CI/CD pipeline
├── jenkins/ # Original Jenkins Pipeline reference scripts
├── src/
│ ├── main/java/com/mycompany/app/App.java
│ └── test/java/com/mycompany/app/AppTest.java
├── Dockerfile # Multi-stage build (Maven → JRE-slim)
├── pom.xml # Maven config, Java 11, JUnit 5
├── script.sh # Deployment script run on the EC2 target
└── README.md
Set these in Settings → Secrets and variables → Actions on the repository:
| Secret | Purpose |
|---|---|
DOCKER_USERNAME |
Your Docker Hub username — used for login and as the image namespace |
DOCKERHUB_TOKEN |
A Docker Hub access token with push permissions |
DEPLOY_SSH_KEY |
Full contents of the private SSH key for the EC2 host |
DEPLOY_HOST |
Public IP or DNS of the target EC2 instance |
You also need to grant the workflow write access (so the Maven version-bump action can commit back): Settings → Actions → General → Workflow permissions → "Read and write permissions" and check "Allow GitHub Actions to create and approve pull requests".
The EC2 instance just needs Docker installed and the workflow's public key in ~/.ssh/authorized_keys:
sudo apt update
sudo apt install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker ubuntuThe script.sh that runs on the target uses an IMAGE env var, defaulting to <your-dockerhub-user>/java-ci-cd:latest. The workflow passes the real image name through over SSH, so the script stays portable.
mvn clean package # → target/my-app-<version>.jar
java -jar target/my-app-*.jar
# → "Hello World!"Run the tests:
mvn testBuild the Docker image locally:
docker build -t my-java-app:latest .
docker run --rm my-java-app:latest- The base Java code (
App.java,AppTest.java,pom.xml,Dockerfile) was originally based on the Jenkins "Build a Java app with Maven" tutorial. The CI/CD pipeline, multi-stage Docker build, Dependabot config, and SSH deployment flow are the original work for this project. - The Maven version-bump action commits the new
pom.xmlback tomain. The action is configured to skip its own commits, so you don't get an infinite workflow loop. - Image tag is always
:latest. For production you'd want to tag with the bumped Maven version too — easy enhancement on top of this.