DEV Community

Cover image for Deploying Spring Boot to AWS EC2 with Docker and GitHub Actions — The Repeatable Way
Yadrs
Yadrs

Posted on

Deploying Spring Boot to AWS EC2 with Docker and GitHub Actions — The Repeatable Way

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
Enter fullscreen mode Exit fullscreen mode

At a high level, the moving parts are:

Spring Boot app
Docker
GitHub Actions
AWS EC2
Environment variables
Deployment script
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

The important principle:

Do not hardcode production secrets in source code.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This is intentionally simplified.

A real workflow may also handle:

  • private repositories
  • GitHub personal access tokens
  • production .env creation
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and:

Full Kubernetes / enterprise platform setup
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

GitHub (feedback and stars appreciated):

https://github.com/springgen-dev/springgen

App:

https://app.springgen.dev


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.