Deploying a Spring Boot application to AWS EC2 is straightforward once you've done it.
Getting there the first time — and repeating the setup on your next few projects — is where the real cost appears.
A production deployment is usually not just about running a JAR file.
For a real-world backend, deployment often involves:
- separating local and production configuration
- building the application consistently
- packaging it with Docker
- managing environment variables and secrets
- connecting to an EC2 instance
- restarting the app safely
- automating the process after every push
This guide walks through the deployment workflow at a high level, the decisions involved, and why this setup becomes repetitive across projects.
The goal is not to hide the details. You should understand your deployment pipeline.
The real pain starts when you have to rebuild the same foundation again and again.
The Deployment Flow
A typical Spring Boot EC2 deployment with GitHub Actions looks like this:
Push to main branch
↓
GitHub Actions runs cd.yml
↓
GitHub Actions connects to EC2 using SSH
↓
EC2 pulls the latest code
↓
Production environment variables are prepared
↓
Deployment script runs
↓
Docker rebuilds and restarts the application
At a high level, the moving parts are:
Spring Boot app
Docker
GitHub Actions
AWS EC2
Environment variables
Deployment script
Each piece is simple enough on its own.
The complexity comes from wiring them together correctly and repeating that setup for every new backend.
1. Separate Local and Production Configuration
A production-ready Spring Boot project usually needs different configuration for local development and production.
For example:
src/main/resources/
├── application.yml
└── application-prod.yml
Local configuration may use local Docker containers.
Production configuration should rely on environment variables.
Example production config pattern:
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
server:
port: ${SERVER_PORT:8080}
The important principle:
Do not hardcode production secrets in source code.
Use environment variables, GitHub Secrets, AWS Secrets Manager, or another secret management approach.
2. Add Docker Packaging
Docker gives the application a consistent runtime environment.
A Spring Boot deployment usually needs:
Dockerfile
docker-compose.yml
docker-compose.prod.yml
The Dockerfile packages the Spring Boot application.
The Compose files help define how the app runs locally or in production.
A simplified Docker flow looks like:
Build JAR
↓
Build Docker image
↓
Run container
↓
Expose app on port 8080
The exact Dockerfile can vary depending on:
- Java version
- Gradle vs Maven
- multi-stage build preference
- whether you want smaller runtime images
- whether the app needs extra OS packages
The key is consistency.
Once Docker is in place, the app runs the same way every time.
3. Prepare the EC2 Instance
Before GitHub Actions can deploy anything, the EC2
instance needs to be ready to receive it.
An EC2 deployment needs a server with the required runtime tools installed.
At minimum, the EC2 instance usually needs:
- Git
- Docker
- access to the repository
- an SSH user
- firewall/security group allowing app traffic
- environment configuration for production
The deployment process should avoid manually copying files whenever possible.
Instead, the EC2 instance can pull the latest code from the repository and run a deployment script.
That keeps the deployment repeatable.
4. Use GitHub Actions as the Deployment Trigger
GitHub Actions becomes the automation layer.
The workflow usually does this:
Checkout repository
↓
Connect to EC2 over SSH
↓
Pull latest code on EC2
↓
Create or update production environment variables
↓
Run deployment script
A simplified workflow shape looks like this:
name: Deploy Spring Boot
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd my-app
git pull origin main
./scripts/deploy-ec2.sh
This is intentionally simplified.
A real workflow may also handle:
- private repositories
- GitHub personal access tokens
- production
.envcreation - Docker installation checks
- branch-specific deployments
- build validation
- rollback strategy
5. Use a Deployment Script on EC2
Instead of putting all deployment logic directly inside GitHub Actions, it is cleaner to keep the actual app deployment logic in a script inside the project.
Example:
scripts/deploy-ec2.sh
A deployment script typically handles:
- stopping the existing container
- removing old containers/images when needed
- rebuilding the Docker image
- starting the updated container
- loading production environment variables
- keeping deployment commands consistent
A simplified script flow:
Read environment config
↓
Stop existing container
↓
Build latest Docker image
↓
Start new container
↓
Verify app is running
This is useful because the script can be used in two ways:
Automated:
GitHub Actions runs it during CI/CD
Manual:
SSH into EC2 and run it directly
That gives flexibility without duplicating deployment logic.
6. Manage Production Environment Variables
Production deployments usually need a .env.prod or equivalent environment configuration.
Typical values include:
APP_NAME
SERVER_PORT
SPRING_PROFILES_ACTIVE
SPRING_DATASOURCE_URL
SPRING_DATASOURCE_USERNAME
SPRING_DATASOURCE_PASSWORD
JWT_SECRET
OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET
CORS_ALLOWED_ORIGINS
There are two common approaches.
Option A — GitHub Actions creates the production env file
GitHub Secrets store sensitive values.
During deployment, GitHub Actions writes those secrets into .env.prod on the EC2 instance.
This keeps secrets out of source code.
Option B — Create .env.prod manually once on EC2
For a simpler setup, you can SSH into EC2 and create the production env file once.
Then future deployments reuse it.
This is useful for smaller projects or manual deployments.
7. What This Looks Like End-to-End
The complete deployment system looks like this:
Developer
↓
Pushes code to main
↓
GitHub Actions starts
↓
SSH connection to EC2
↓
EC2 pulls latest code
↓
Production env config is prepared
↓
scripts/deploy-ec2.sh runs
↓
Docker rebuilds app
↓
Old container is replaced
↓
Spring Boot app runs on EC2
This is a practical setup for:
- side projects
- MVPs
- internal tools
- freelance client backends
- small SaaS applications
It is not the only way to deploy Spring Boot.
But it is a useful middle ground between:
Manual SSH deployment
and:
Full Kubernetes / enterprise platform setup
Why This Becomes Repetitive
The first time you set this up, it is a learning exercise.
The third or fourth time, it becomes repetitive.
Every new Spring Boot backend needs similar decisions:
- project structure
- Docker setup
- production config
- deployment workflow
- CI/CD files
- environment variables
- security defaults
- logging configuration
None of these pieces are impossible.
But wiring them together correctly takes time.
And if you are building multiple projects, client backends, SaaS ideas, or microservices, that setup cost repeats.
Automating Repetitive Backend Setup
This is the kind of repetitive backend setup that SpringGen is designed to automate.
SpringGen creates a ready-to-customize Spring Boot foundation with:
- layered backend structure
- database configuration
- authentication scaffolding
- Docker setup
- GitHub Actions workflows
- AWS EC2 deployment automation
- security hardening
- logging configuration
The goal is not to replace understanding Spring Boot or DevOps.
The goal is to remove the repetitive setup after you already know what kind of backend foundation you want.
Generated projects are standard Spring Boot code:
No generator runtime dependency
No framework lock-in
No frontend opinions
You generate the project, then own and modify the code like any normal Spring Boot application.
Try SpringGen
Generate a Starter Spring Boot project locally:
Free CLI:
npm install -g springgen
springgen init
GitHub (feedback and stars appreciated):
https://github.com/springgen-dev/springgen
App:
Final Thoughts
A good deployment pipeline is not just about pushing code to a server.
It is about making deployment predictable, repeatable, and easy to maintain.
You can absolutely build every piece of this manually — and understanding how it works is important.
But after you have configured the same Docker setup, CI/CD workflow, environment files, and deployment scripts across multiple projects, the setup itself becomes the repetitive part.
That repetition is what SpringGen is designed to remove.
Spend less time rebuilding the foundation, and more time building the application.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.