Careeroza
GitHub Actions per Service
Workflow for order-service
GitHub Actions
In a monorepo, the paths filter makes this workflow run only when order-service changes:
.github/workflows/order-service.yml
name: order-service
on:
push:
branches: [main]
paths:
- "order-service/**"
- ".github/workflows/order-service.yml"
pull_request:
paths:
- "order-service/**"
env:
IMAGE: ghcr.io/shopease/order-service
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
defaults:
run:
working-directory: order-service
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- name: Test (unit + Testcontainers integration + contract)
run: ./mvnw -B verify
- name: Log in to GitHub Container Registry
if: github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@v6
with:
context: order-service
push: true
tags: ${{ env.IMAGE }}:${{ github.sha }}
deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: azure/setup-kubectl@v4
- name: Roll out the new image
env:
KUBECONFIG_DATA: ${{ secrets.STAGING_KUBECONFIG }}
run: |
echo "$KUBECONFIG_DATA" > kubeconfig && export KUBECONFIG=kubeconfig
kubectl -n shopease set image deployment/order-service order-service=${{ env.IMAGE }}:${{ github.sha }}
kubectl -n shopease rollout status deployment/order-service --timeout=5m
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # GitHub "required reviewers" = manual approval gate
steps:
- run: echo "same steps as staging, with the production kubeconfig"Example output
order-service #482 main a9c41e7 build passed 3m 12s (Tests run: 148, Failures: 0) deploy-staging passed 1m 05s deployment "order-service" successfully rolled out deploy-production waiting for approval from @shopease/payments-leads
Keep 15 Pipelines DRY with a Reusable Workflow
Calling a reusable workflow
Put the common steps in one reusable workflow and call it from each service's small workflow:
.github/workflows/payment-service.yml
# .github/workflows/payment-service.yml
name: payment-service
on:
push:
branches: [main]
paths: ["payment-service/**"]
jobs:
pipeline:
uses: ./.github/workflows/java-service.yml # has: on: workflow_call
with:
service: payment-service
secrets: inherit- Add an image scan step (Trivy, Grype) and fail on critical vulnerabilities.
- Store only immutable tags in production: the Git SHA or a release version, never
latest. - Many teams use GitOps: CI only updates the image tag in a deployment repo, and Argo CD or Flux syncs the cluster to that repo.
Database Migrations in the Pipeline
Each service owns its schema, so each service runs its own Flyway or Liquibase migrations at startup (see the Hibernate & JPA material). During a rolling update old and new Pods run at the same time, so every migration must work with both versions. Use expand and contract:
- Expand (release 1): add the new column
delivery_slotas nullable; new code writes both old and new columns. - Migrate: backfill old rows with a batch job.
- Contract (release 2, after all Pods run the new code): stop using the old column, then drop it.
Note: Never rename or drop a column in the same release that stops using it. A rollback to the previous version would crash.