1name: Deploy
2
3on:
4 workflow_dispatch:
5 inputs:
6 environment: { type: environment, required: true } # pick staging/production
7 tag: { type: string, required: true, description: 'image tag, e.g. 1.4.0' }
8
9permissions:
10 id-token: write # OIDC token for AWS
11 contents: read
12
13concurrency:
14 group: deploy-${{ inputs.environment }} # one deploy per env
15 cancel-in-progress: false # never kill a running deploy
16
17jobs:
18 deploy:
19 runs-on: ubuntu-latest
20 environment:
21 name: ${{ inputs.environment }} # loads that env's vars/secrets + approval
22 url: https://${{ vars.APP_HOST }}
23 timeout-minutes: 20
24 steps:
25 - uses: aws-actions/configure-aws-credentials@v6
26 with:
27 role-to-assume: ${{ vars.AWS_ROLE_ARN }}
28 aws-region: ${{ vars.AWS_REGION }}
29
30 - name: Deploy via SSM
31 id: ssm
32 run: |
33 CMD_ID=$(aws ssm send-command \
34 --instance-ids "${{ vars.INSTANCE_ID }}" \
35 --document-name AWS-RunShellScript \
36 --parameters commands="cd /opt/app && echo PREV=\$(cat .tag 2>/dev/null) > .prev && echo ${{ inputs.tag }} > .tag && TAG=${{ inputs.tag }} docker compose pull api && TAG=${{ inputs.tag }} docker compose up -d api" \
37 --query Command.CommandId --output text)
38 aws ssm wait command-executed --command-id "$CMD_ID" --instance-id "${{ vars.INSTANCE_ID }}"
39
40 - name: Smoke test
41 run: |
42 for i in $(seq 1 20); do
43 curl -fsS "https://${{ vars.APP_HOST }}/actuator/health" && exit 0
44 sleep 6
45 done
46 exit 1
47
48 - name: Roll back
49 if: failure() && steps.ssm.outcome == 'success'
50 run: |
51 aws ssm send-command --instance-ids "${{ vars.INSTANCE_ID }}" \
52 --document-name AWS-RunShellScript \
53 --parameters commands="cd /opt/app && . ./.prev && echo \$PREV > .tag && TAG=\$PREV docker compose up -d api"