1010jobs :
1111 deploy :
1212 runs-on : ubuntu-latest
13+ timeout-minutes : 30
1314
1415 steps :
1516 - name : Checkout code
@@ -21,10 +22,12 @@ jobs:
2122 echo "ENVIRONMENT=prod" >> $GITHUB_ENV
2223 echo "STACK_NAME=neoapi-prod" >> $GITHUB_ENV
2324 echo "RDSDB=neotoma" >> $GITHUB_ENV
25+ echo "ENDPOINT=api.neotomadb.org" >> $GITHUB_ENV
2426 else
2527 echo "ENVIRONMENT=dev" >> $GITHUB_ENV
2628 echo "STACK_NAME=neoapi-dev" >> $GITHUB_ENV
2729 echo "RDSDB=neotomatank" >> $GITHUB_ENV
30+ echo "ENDPOINT=api-dev.neotomadb.org" >> $GITHUB_ENV
2831 fi
2932
3033 - name : Configure AWS credentials
@@ -40,12 +43,14 @@ jobs:
4043
4144 - name : Create ECR repository if it doesn't exist
4245 run : |
43- aws ecr describe-repositories --repository-names neoapi-${{ env.ENVIRONMENT }} --region ${{ env.AWS_REGION }} 2>/dev/null || \
46+ aws ecr describe-repositories \
47+ --repository-names neoapi-${{ env.ENVIRONMENT }} \
48+ --region ${{ env.AWS_REGION }} 2>/dev/null || \
4449 aws ecr create-repository \
4550 --repository-name neoapi-${{ env.ENVIRONMENT }} \
4651 --region ${{ env.AWS_REGION }} \
47- --image-scanning-configuration scanOnPush=true
48-
52+ --image-scanning-configuration scanOnPush=true \
53+ --encryption-configuration encryptionType=AES256
4954 - name : Build, tag, and push image to Amazon ECR
5055 run : |
5156 ECR_REPOSITORY=${{ steps.login-ecr.outputs.registry }}/neoapi-${{ env.ENVIRONMENT }}
6267 echo "IMAGE_URI=$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_ENV
6368 echo "ECR_REPOSITORY=$ECR_REPOSITORY" >> $GITHUB_ENV
6469
70+ - name : Debug parameters
71+ run : |
72+ echo "Subnets value: '${{ secrets.PRIVATE_SUBNETS }}'"
73+ echo "Length: ${#PRIVATE_SUBNETS}"
74+ PRIVATE_SUBNETS="${{ secrets.PRIVATE_SUBNETS }}"
75+ if [ -z "$PRIVATE_SUBNETS" ]; then
76+ echo "ERROR: PRIVATE_SUBNETS is empty!"
77+ exit 1
78+ fi
79+ echo "SUBNET_LIST=subnet-0e66614ca7e9e7247,subnet-013f8ff069404c987" >> $GITHUB_ENV
80+
6581 - name : Deploy CloudFormation stack
6682 run : |
6783 echo "Deploying infrastructure with image: ${{ env.IMAGE_URI }}"
@@ -70,23 +86,18 @@ jobs:
7086 --template-file infrastructure/cloudformation-template.yaml \
7187 --stack-name ${{ env.STACK_NAME }} \
7288 --parameter-overrides \
73- Environment=${{ env.ENVIRONMENT }} \
74- ImageUri=${{ env.IMAGE_URI }} \
75- RDSHostname=${{ secrets.RDS_HOSTNAME }} \
76- RDSDatabase=${{ env.RDSDB }} \
77- RDSUsername=${{ secrets.RDS_USERNAME }} \
78- RDSPassword=${{ secrets.RDS_PASSWORD }} \
79- VPCId=${{ secrets.VPC_ID }} \
80- PrivateSubnets=${{ secrets.PRIVATE_SUBNETS }} \
89+ "Environment=${{ env.ENVIRONMENT }}" \
90+ "ImageUri=${{ env.IMAGE_URI }}" \
91+ "RDSHostname=${{ secrets.RDS_HOSTNAME }}" \
92+ "RDSDatabase=${{ env.RDSDB }}" \
93+ "RDSUsername=${{ secrets.RDS_USERNAME }}" \
94+ "RDSPassword=${{ secrets.RDS_PASSWORD }}" \
95+ "VPCId=${{ secrets.VPC_ID }}" \
96+ "PrivateSubnets=${{ env.SUBNET_LIST }}" \
97+ "DomainName=${{ env.ENDPOINT }}" \
8198 --capabilities CAPABILITY_NAMED_IAM \
82- --region ${{ env.AWS_REGION }}
83-
84- - name : Wait for deployment to complete
85- run : |
86- echo "Waiting for CloudFormation stack to complete..."
87- aws cloudformation wait stack-create-complete \
88- --stack-name ${{ env.STACK_NAME }} \
89- --region ${{ env.AWS_REGION }}
99+ --region ${{ env.AWS_REGION }} \
100+ --no-fail-on-empty-changeset
90101
91102 - name : Get ECR repository URI
92103 run : |
@@ -105,28 +116,63 @@ jobs:
105116 --output text \
106117 --region ${{ env.AWS_REGION }})
107118
119+ echo "SERVICE_URL=$SERVICE_URL" >> $GITHUB_ENV
120+ echo "service_url=$SERVICE_URL" >> $GITHUB_OUTPUT
121+
108122 echo "🚀 Deployment complete!"
109123 echo "Service URL: $SERVICE_URL"
110124 echo "Environment: ${{ env.ENVIRONMENT }}"
111125 echo "Image: ${{ env.IMAGE_URI }}"
112126
113127 - name : Health check
114128 run : |
115- SERVICE_URL=$(aws cloudformation describe-stacks \
116- --stack-name ${{ env.STACK_NAME }} \
117- --query 'Stacks[0].Outputs[?OutputKey==`ServiceUrl`].OutputValue' \
118- --output text \
119- --region ${{ env.AWS_REGION }})
120-
121- echo "Performing health check..."
122- sleep 30 # Give the service time to start
123-
124- for i in {1..5}; do
125- if curl -f -s "${SERVICE_URL}/v2.0/routes/healthwatch" > /dev/null; then
126- echo "✅ Health check passed!"
129+ SERVICE_URL="${{ env.SERVICE_URL }}"
130+ HEALTH_ENDPOINT="${SERVICE_URL}/healthcheck"
131+
132+ echo "Performing health check on: $HEALTH_ENDPOINT"
133+ echo "Waiting 60 seconds for App Runner to start..."
134+ sleep 60
135+
136+ MAX_ATTEMPTS=10
137+ ATTEMPT=1
138+
139+ while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
140+ echo "Health check attempt $ATTEMPT/$MAX_ATTEMPTS..."
141+
142+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_ENDPOINT" || echo "000")
143+
144+ if [ "$HTTP_CODE" = "200" ]; then
145+ echo "✅ Health check passed! (HTTP $HTTP_CODE)"
127146 exit 0
128147 else
129- echo "⏳ Health check attempt $i/5 failed, retrying in 30 seconds..."
148+ echo "⏳ Health check failed with HTTP code: $HTTP_CODE"
149+
150+ if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
151+ echo "❌ Health check failed after $MAX_ATTEMPTS attempts"
152+ exit 1
153+ fi
154+
155+ echo "Retrying in 30 seconds..."
130156 sleep 30
131157 fi
158+
159+ ATTEMPT=$((ATTEMPT + 1))
132160 done
161+
162+ - name : Notify deployment status
163+ if : always()
164+ run : |
165+ if [ "${{ job.status }}" = "success" ]; then
166+ echo "✅ Deployment successful for ${{ env.ENVIRONMENT }}"
167+ echo "URL: ${{ env.SERVICE_URL }}"
168+ else
169+ echo "❌ Deployment failed for ${{ env.ENVIRONMENT }}"
170+ fi
171+
172+ - name : Rollback on failure
173+ if : failure()
174+ run : |
175+ echo "Deployment failed, initiating rollback..."
176+ aws cloudformation cancel-update-stack \
177+ --stack-name ${{ env.STACK_NAME }} \
178+ --region ${{ env.AWS_REGION }} || true
0 commit comments