Why Automate Deployments?
Manual deployments are error-prone and time-consuming. A CI/CD pipeline gives you confidence that every change has passed tests before it reaches production.
The Pipeline Structure
My GitHub Actions workflow has three jobs that run in sequence:
- test-backend — Runs Gradle tests on Java 21
- test-frontend — Runs pnpm build on Node 22
- docker-build — Builds both Docker images (only if tests pass)
jobs:
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '21', distribution: 'temurin' }
- run: cd backend && ./gradlew test
test-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: cd frontend && npm i -g pnpm && pnpm install && pnpm build
Multi-Stage Docker Builds
Multi-stage builds keep image sizes small. For the Spring Boot backend, the final image is only ~200MB:
# Stage 1: Build
FROM gradle:8.5-jdk21 AS builder
WORKDIR /app
COPY . .
RUN gradle bootJar --no-daemon
# Stage 2: Run
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
Key Takeaways
- Always run tests before building Docker images
- Use multi-stage builds to minimize image size
- Store secrets in GitHub Actions secrets, never in code