ci: use dind container for build, alpine+ssh for deploy, fix runner label
Some checks failed
CI/CD → Deploy via SSH / Build & Push Docker Images (push) Failing after 30s
CI/CD → Deploy via SSH / Deploy via SSH (push) Has been skipped
CI/CD → Deploy via SSH / Validate HTTPS & Endpoints (push) Has been skipped

This commit is contained in:
MatheusAlves96 2026-04-21 00:49:05 -03:00
parent 8a29858285
commit caf541d750

View file

@ -12,55 +12,55 @@ env:
jobs: jobs:
# ──────────────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────────────
# 1. BUILD & PUSH IMAGES # 1. BUILD & PUSH IMAGES (Docker-in-Docker)
# ──────────────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────────────
build: build:
name: Build & Push Docker Images name: Build & Push Docker Images
runs-on: self-hosted runs-on: docker
container:
image: docker:27-dind
env:
DOCKER_TLS_CERTDIR: ""
options: --privileged
steps: steps:
- name: Install git
run: apk add --no-cache git
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set image tag - name: Set image tag
id: tag id: tag
run: echo "IMAGE_TAG=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT run: echo "IMAGE_TAG=$(echo $GITHUB_SHA | cut -c1-8)" >> $GITHUB_OUTPUT
- name: Log in to Forgejo Container Registry - name: Log in to registry
uses: docker/login-action@v3 run: |
with: echo "${{ secrets.REGISTRY_PASSWORD }}" | \
registry: ${{ vars.REGISTRY }} docker login ${{ vars.REGISTRY }} -u ${{ secrets.REGISTRY_USER }} --password-stdin
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build & push backend - name: Build & push backend
uses: docker/build-push-action@v5 run: |
with: TAG=${{ steps.tag.outputs.IMAGE_TAG }}
context: ./backend docker build \
file: ./backend/Dockerfile.prod -f backend/Dockerfile.prod \
push: true -t ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:${TAG} \
tags: | -t ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:latest \
${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:${{ steps.tag.outputs.IMAGE_TAG }} ./backend
${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:latest docker push ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:${TAG}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:cache docker push ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:latest
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:cache,mode=max
- name: Build & push frontend - name: Build & push frontend
uses: docker/build-push-action@v5 run: |
with: TAG=${{ steps.tag.outputs.IMAGE_TAG }}
context: ./frontend docker build \
file: ./frontend/Dockerfile.prod -f frontend/Dockerfile.prod \
push: true --build-arg VITE_API_URL=https://${{ vars.DOMAIN }}/api \
build-args: | -t ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:${TAG} \
VITE_API_URL=https://${{ vars.DOMAIN }}/api -t ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:latest \
tags: | ./frontend
${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:${{ steps.tag.outputs.IMAGE_TAG }} docker push ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:${TAG}
${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:latest docker push ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:latest
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:cache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:cache,mode=max
outputs: outputs:
image_tag: ${{ steps.tag.outputs.IMAGE_TAG }} image_tag: ${{ steps.tag.outputs.IMAGE_TAG }}
@ -70,52 +70,48 @@ jobs:
# ──────────────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────────────
deploy: deploy:
name: Deploy via SSH name: Deploy via SSH
runs-on: self-hosted runs-on: docker
needs: build needs: build
container:
image: alpine:3.19
steps: steps:
- name: Install SSH tools
run: apk add --no-cache openssh-client
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup SSH key - name: Setup SSH key
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ vars.SSH_PORT || '22' }} ${{ vars.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan -p ${{ vars.SSH_PORT }} ${{ vars.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: Copy docker-compose.prod.yml to server - name: Copy compose file to server
run: | run: |
scp -i ~/.ssh/deploy_key \ scp -i ~/.ssh/deploy_key -P ${{ vars.SSH_PORT }} \
-P ${{ vars.SSH_PORT || '22' }} \
docker-compose.prod.yml \ docker-compose.prod.yml \
${{ vars.SSH_USER }}@${{ vars.SSH_HOST }}:/opt/saas-imobiliaria/docker-compose.prod.yml ${{ vars.SSH_USER }}@${{ vars.SSH_HOST }}:/opt/saas-imobiliaria/docker-compose.prod.yml
- name: Deploy on server (Docker Swarm stack) - name: Deploy Swarm stack on server
env:
IMAGE_TAG: ${{ needs.build.outputs.image_tag }}
run: | run: |
ssh -i ~/.ssh/deploy_key \ ssh -i ~/.ssh/deploy_key -p ${{ vars.SSH_PORT }} \
-p ${{ vars.SSH_PORT || '22' }} \
${{ vars.SSH_USER }}@${{ vars.SSH_HOST }} \ ${{ vars.SSH_USER }}@${{ vars.SSH_HOST }} \
bash -s << ENDSSH "set -e
mkdir -p /opt/saas-imobiliaria
cd /opt/saas-imobiliaria
set -e echo '${{ secrets.REGISTRY_PASSWORD }}' | \
docker login ${{ vars.REGISTRY }} -u ${{ secrets.REGISTRY_USER }} --password-stdin
DEPLOY_DIR="/opt/saas-imobiliaria" docker pull ${{ vars.REGISTRY }}/saas-imobiliaria-backend:${IMAGE_TAG}
mkdir -p "\$DEPLOY_DIR" docker pull ${{ vars.REGISTRY }}/saas-imobiliaria-frontend:${IMAGE_TAG}
cd "\$DEPLOY_DIR"
# Log in to registry IMAGE_TAG=${IMAGE_TAG} \
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ${{ vars.REGISTRY }} \
-u ${{ secrets.REGISTRY_USER }} \
--password-stdin
# Pull images explicitly so swarm has them cached
docker pull ${{ vars.REGISTRY }}/saas-imobiliaria-backend:${{ needs.build.outputs.image_tag }}
docker pull ${{ vars.REGISTRY }}/saas-imobiliaria-frontend:${{ needs.build.outputs.image_tag }}
# Deploy stack with env vars inline
IMAGE_TAG=${{ needs.build.outputs.image_tag }} \
REGISTRY=${{ vars.REGISTRY }} \ REGISTRY=${{ vars.REGISTRY }} \
DOMAIN=${{ vars.DOMAIN }} \ DOMAIN=${{ vars.DOMAIN }} \
POSTGRES_DB=${{ secrets.POSTGRES_DB }} \ POSTGRES_DB=${{ secrets.POSTGRES_DB }} \
@ -129,75 +125,70 @@ jobs:
--prune \ --prune \
saas-imobiliaria saas-imobiliaria
echo "Stack deployed: ${{ needs.build.outputs.image_tag }}" sleep 15
docker stack services saas-imobiliaria"
# Wait for services to converge
sleep 10
docker stack services saas-imobiliaria
ENDSSH
# ──────────────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────────────
# 3. HEALTH CHECK — valida HTTPS + endpoints críticos # 3. HEALTH CHECK
# ──────────────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────────────
healthcheck: healthcheck:
name: Validate HTTPS & Endpoints name: Validate HTTPS & Endpoints
runs-on: self-hosted runs-on: docker
needs: deploy needs: [build, deploy]
container:
image: alpine:3.19
steps: steps:
- name: Wait for containers to stabilize - name: Install tools
run: sleep 30 run: apk add --no-cache curl openssl
- name: Check frontend HTTPS - name: Wait for stack to stabilize
run: | run: sleep 40
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 15 "https://${{ vars.DOMAIN }}")
echo "Frontend: $STATUS"
[ "$STATUS" = "200" ] || (echo "❌ Frontend HTTPS falhou ($STATUS)" && exit 1)
- name: Check HTTP → HTTPS redirect - name: Frontend HTTPS
run: | run: |
LOCATION=$(curl -s -o /dev/null -w "%{redirect_url}" \ S=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "https://${{ vars.DOMAIN }}")
--max-time 10 "http://${{ vars.DOMAIN }}") echo "Frontend: $S"
echo "Redirect: $LOCATION" [ "$S" = "200" ] || (echo "❌ Frontend falhou ($S)" && exit 1)
echo "$LOCATION" | grep -q "https://" || (echo "❌ Redirect HTTP→HTTPS ausente" && exit 1)
- name: Check backend /health - name: HTTP → HTTPS redirect
run: | run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ L=$(curl -s -o /dev/null -w "%{redirect_url}" --max-time 10 "http://${{ vars.DOMAIN }}")
--max-time 15 "https://${{ vars.DOMAIN }}/api/health") echo "Redirect: $L"
echo "Backend health: $STATUS" echo "$L" | grep -q "https://" || (echo "❌ Redirect ausente" && exit 1)
[ "$STATUS" = "200" ] || (echo "❌ Backend health check falhou ($STATUS)" && exit 1)
- name: Check TLS certificate validity - name: Backend /api/health
run: | run: |
EXPIRY=$(echo | openssl s_client \ R=$(curl -s --max-time 15 "https://${{ vars.DOMAIN }}/api/health")
-connect ${{ vars.DOMAIN }}:443 \ S=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "https://${{ vars.DOMAIN }}/api/health")
echo "Health: $S → $R"
[ "$S" = "200" ] || (echo "❌ Health falhou ($S)" && exit 1)
echo "$R" | grep -q '"db": "ok"' || (echo "❌ DB não conectado" && exit 1)
- name: TLS certificate validity
run: |
EXPIRY=$(echo | openssl s_client -connect ${{ vars.DOMAIN }}:443 \
-servername ${{ vars.DOMAIN }} 2>/dev/null \ -servername ${{ vars.DOMAIN }} 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2) | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
echo "Certificado expira: $EXPIRY" echo "Expira: $EXPIRY"
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s) DAYS=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
NOW_EPOCH=$(date +%s) echo "Dias restantes: $DAYS"
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 )) [ "$DAYS" -gt 7 ] || (echo "❌ Cert expira em $DAYS dias" && exit 1)
echo "Dias restantes: $DAYS_LEFT"
[ "$DAYS_LEFT" -gt 7 ] || (echo "❌ Certificado expira em $DAYS_LEFT dias!" && exit 1)
- name: Check API properties endpoint - name: GET /api/properties
run: | run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ S=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "https://${{ vars.DOMAIN }}/api/properties?limit=1")
--max-time 15 "https://${{ vars.DOMAIN }}/api/properties?limit=1") echo "Properties: $S"
echo "Properties API: $STATUS" [ "$S" = "200" ] || (echo "❌ Properties falhou ($S)" && exit 1)
[ "$STATUS" = "200" ] || (echo "❌ Properties API falhou ($STATUS)" && exit 1)
- name: Check deployed version matches image tag - name: Version tag matches
run: | run: |
RESPONSE=$(curl -s --max-time 10 "https://${{ vars.DOMAIN }}/api/version") R=$(curl -s --max-time 10 "https://${{ vars.DOMAIN }}/api/version")
echo "Version response: $RESPONSE" echo "Version: $R"
echo "$RESPONSE" | grep -q "${{ needs.build.outputs.image_tag }}" \ echo "$R" | grep -q "${{ needs.build.outputs.image_tag }}" \
|| (echo "❌ Version tag não confere — esperado: ${{ needs.build.outputs.image_tag }}" && exit 1) || (echo "❌ Tag não confere — esperado ${{ needs.build.outputs.image_tag }}" && exit 1)
- name: All checks passed - name: Summary
run: | run: |
echo "✅ Deploy validado com sucesso!" echo "✅ Todos os checks passaram!"
echo " → https://${{ vars.DOMAIN }} está no ar" echo " → https://${{ vars.DOMAIN }} está no ar com tag ${{ needs.build.outputs.image_tag }}"