1- name : Deploy Backend
1+ name : Deploy Backend to AWS ECS
22
33on :
44 push :
5- branches : [main]
5+ branches :
6+ - main
7+ - develop
68 paths :
79 - " backend/**"
810 - " .github/workflows/deploy-backend.yml"
11+ workflow_dispatch :
912
1013env :
1114 AWS_REGION : us-east-1
12- ECR_REPOSITORY : kickwatch-backend
13- ECS_CLUSTER : kickwatch-cluster
14- ECS_SERVICE : kickwatch-backend-service
15- CONTAINER_NAME : kickwatch-backend
1615
1716jobs :
18- deploy :
17+ build-and-deploy :
18+ name : Build and Deploy
1919 runs-on : ubuntu-latest
20- defaults :
21- run :
22- working-directory : backend
20+ permissions :
21+ contents : read
22+ id-token : write
23+
24+ env :
25+ IS_PROD : ${{ github.ref == 'refs/heads/main' }}
26+
2327 steps :
28+ - name : Set environment variables
29+ run : |
30+ if [ "${{ env.IS_PROD }}" = "true" ]; then
31+ echo "ECR_REPOSITORY=kickwatch-api" >> $GITHUB_ENV
32+ echo "ECS_CLUSTER=kickwatch-cluster" >> $GITHUB_ENV
33+ echo "ECS_SERVICE=kickwatch-api-service" >> $GITHUB_ENV
34+ echo "CONTAINER_NAME=kickwatch-api" >> $GITHUB_ENV
35+ echo "DEPLOY_ENV=production" >> $GITHUB_ENV
36+ echo "SECRET_PREFIX=kickwatch" >> $GITHUB_ENV
37+ echo "LOG_GROUP=/ecs/kickwatch-api" >> $GITHUB_ENV
38+ echo "GIN_MODE=release" >> $GITHUB_ENV
39+ else
40+ echo "ECR_REPOSITORY=kickwatch-api-dev" >> $GITHUB_ENV
41+ echo "ECS_CLUSTER=kickwatch-cluster-dev" >> $GITHUB_ENV
42+ echo "ECS_SERVICE=kickwatch-api-dev-service" >> $GITHUB_ENV
43+ echo "CONTAINER_NAME=kickwatch-api-dev" >> $GITHUB_ENV
44+ echo "DEPLOY_ENV=development" >> $GITHUB_ENV
45+ echo "SECRET_PREFIX=kickwatch-dev" >> $GITHUB_ENV
46+ echo "LOG_GROUP=/ecs/kickwatch-api-dev" >> $GITHUB_ENV
47+ echo "GIN_MODE=debug" >> $GITHUB_ENV
48+ fi
49+
2450 - uses : actions/checkout@v4
2551
2652 - uses : actions/setup-go@v5
2753 with :
28- go-version : " 1.24 "
54+ go-version-file : backend/go.mod
2955 cache-dependency-path : backend/go.sum
3056
31- - run : go test ./...
57+ - name : Run tests and vet
58+ working-directory : backend
59+ run : |
60+ go vet ./... &
61+ VET_PID=$!
62+ go test ./... &
63+ TEST_PID=$!
64+ wait $VET_PID || exit 1
65+ wait $TEST_PID || exit 1
66+
67+ - name : Build Go binary
68+ working-directory : backend
69+ run : CGO_ENABLED=0 GOOS=linux go build -o api ./cmd/api
3270
33- - name : Configure AWS credentials
71+ - name : Configure AWS credentials (OIDC)
3472 uses : aws-actions/configure-aws-credentials@v4
3573 with :
36- aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }}
37- aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
74+ role-to-assume : ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
3875 aws-region : ${{ env.AWS_REGION }}
3976
40- - name : Login to ECR
77+ - name : Login to Amazon ECR
4178 id : login-ecr
4279 uses : aws-actions/amazon-ecr-login@v2
4380
44- - name : Build, tag, push image
81+ - name : Ensure ECR repository exists
82+ run : |
83+ aws ecr describe-repositories --repository-names $ECR_REPOSITORY --region $AWS_REGION 2>/dev/null || \
84+ aws ecr create-repository --repository-name $ECR_REPOSITORY --region $AWS_REGION \
85+ --image-scanning-configuration scanOnPush=true
86+
87+ - name : Set up Docker Buildx
88+ uses : docker/setup-buildx-action@v3
89+
90+ - name : Build and push Docker image
4591 id : build-image
92+ uses : docker/build-push-action@v6
93+ with :
94+ context : backend
95+ file : backend/Dockerfile.ci
96+ push : true
97+ tags : |
98+ ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
99+ ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest
100+ cache-from : type=gha
101+ cache-to : type=gha,mode=max
102+ provenance : false
103+
104+ - name : Resolve Secrets Manager ARNs
105+ id : secrets
46106 env :
47- ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }}
48- IMAGE_TAG : ${{ github.sha }}
107+ AWS_ACCOUNT_ID : ${{ secrets.AWS_ACCOUNT_ID }}
49108 run : |
50- docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
51- docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
52- echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
109+ get_arn() { aws secretsmanager describe-secret --secret-id "$1" --region $AWS_REGION --query "ARN" --output text; }
110+ echo "db_arn=$(get_arn ${SECRET_PREFIX}/database-url)" >> $GITHUB_OUTPUT
111+ echo "apns_key_id_arn=$(get_arn ${SECRET_PREFIX}/apns-key-id)" >> $GITHUB_OUTPUT
112+ echo "apns_team_id_arn=$(get_arn ${SECRET_PREFIX}/apns-team-id)" >> $GITHUB_OUTPUT
113+ echo "apns_bundle_id_arn=$(get_arn ${SECRET_PREFIX}/apns-bundle-id)" >> $GITHUB_OUTPUT
114+ echo "apns_key_arn=$(get_arn ${SECRET_PREFIX}/apns-key)" >> $GITHUB_OUTPUT
53115
54- - name : Download ECS task definition
116+ - name : Generate ECS task definition
117+ env :
118+ AWS_ACCOUNT_ID : ${{ secrets.AWS_ACCOUNT_ID }}
55119 run : |
56- aws ecs describe-task-definition \
57- --task-definition kickwatch-backend \
58- --query taskDefinition \
59- > task-definition.json
120+ cat > /tmp/task-definition.json <<EOF
121+ {
122+ "family": "${{ env.CONTAINER_NAME }}",
123+ "networkMode": "awsvpc",
124+ "requiresCompatibilities": ["FARGATE"],
125+ "cpu": "256",
126+ "memory": "512",
127+ "executionRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole",
128+ "taskRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/kickwatch-task-role",
129+ "containerDefinitions": [
130+ {
131+ "name": "${{ env.CONTAINER_NAME }}",
132+ "image": "placeholder",
133+ "essential": true,
134+ "portMappings": [
135+ { "containerPort": 8080, "protocol": "tcp" }
136+ ],
137+ "environment": [
138+ { "name": "PORT", "value": "8080" },
139+ { "name": "GIN_MODE", "value": "${{ env.GIN_MODE }}" },
140+ { "name": "APP_ENV", "value": "${{ env.DEPLOY_ENV }}" },
141+ { "name": "APNS_ENV", "value": "${{ env.IS_PROD == 'true' && 'production' || 'sandbox' }}" },
142+ { "name": "APNS_KEY_PATH","value": "/secrets/apns.p8" }
143+ ],
144+ "secrets": [
145+ { "name": "DATABASE_URL", "valueFrom": "${{ steps.secrets.outputs.db_arn }}" },
146+ { "name": "APNS_KEY_ID", "valueFrom": "${{ steps.secrets.outputs.apns_key_id_arn }}" },
147+ { "name": "APNS_TEAM_ID", "valueFrom": "${{ steps.secrets.outputs.apns_team_id_arn }}" },
148+ { "name": "APNS_BUNDLE_ID", "valueFrom": "${{ steps.secrets.outputs.apns_bundle_id_arn }}" }
149+ ],
150+ "readonlyRootFilesystem": true,
151+ "linuxParameters": { "initProcessEnabled": true },
152+ "healthCheck": {
153+ "command": ["CMD-SHELL", "wget -q -O /dev/null http://localhost:8080/api/health || exit 1"],
154+ "interval": 15,
155+ "timeout": 5,
156+ "retries": 3,
157+ "startPeriod": 10
158+ },
159+ "logConfiguration": {
160+ "logDriver": "awslogs",
161+ "options": {
162+ "awslogs-group": "${{ env.LOG_GROUP }}",
163+ "awslogs-region": "${{ env.AWS_REGION }}",
164+ "awslogs-stream-prefix": "ecs"
165+ }
166+ }
167+ }
168+ ]
169+ }
170+ EOF
60171
61- - name : Update ECS task definition with new image
172+ - name : Fill image into task definition
62173 id : task-def
63174 uses : aws-actions/amazon-ecs-render-task-definition@v1
64175 with :
65- task-definition : backend /task-definition.json
176+ task-definition : /tmp /task-definition.json
66177 container-name : ${{ env.CONTAINER_NAME }}
67- image : ${{ steps.build-image .outputs.image }}
178+ image : ${{ steps.login-ecr .outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
68179
69180 - name : Deploy to ECS
70181 uses : aws-actions/amazon-ecs-deploy-task-definition@v2
@@ -73,3 +184,11 @@ jobs:
73184 service : ${{ env.ECS_SERVICE }}
74185 cluster : ${{ env.ECS_CLUSTER }}
75186 wait-for-service-stability : true
187+
188+ - name : Deployment summary
189+ run : |
190+ echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
191+ echo "- **Environment**: ${{ env.DEPLOY_ENV }}" >> $GITHUB_STEP_SUMMARY
192+ echo "- **Cluster**: ${{ env.ECS_CLUSTER }}" >> $GITHUB_STEP_SUMMARY
193+ echo "- **Service**: ${{ env.ECS_SERVICE }}" >> $GITHUB_STEP_SUMMARY
194+ echo "- **Image**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
0 commit comments