-
Notifications
You must be signed in to change notification settings - Fork 6
156 lines (140 loc) · 5.56 KB
/
Copy pathcd.yml
File metadata and controls
156 lines (140 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
name: CD
on:
push:
branches: [ main ]
pull_request_target:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
backend_image: ${{ steps.tags.outputs.backend }}
frontend_image: ${{ steps.tags.outputs.frontend }}
# Only run on PRs originating from this repo to avoid exposing secrets to forks
if: ${{ github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- name: Select ref
run: |
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "REF=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV
else
echo "REF=${GITHUB_SHA}" >> $GITHUB_ENV
fi
- uses: actions/checkout@v4
with:
ref: ${{ env.REF }}
- name: Derive lowercase image repo
run: |
echo "IMAGE_REPO=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set image tags
id: tags
run: |
SHA_TAG=sha-${GITHUB_SHA::7}
echo "backend=${{ env.REGISTRY }}/${IMAGE_REPO}-backend:${SHA_TAG}" >> $GITHUB_OUTPUT
echo "backend_latest=${{ env.REGISTRY }}/${IMAGE_REPO}-backend:latest" >> $GITHUB_OUTPUT
echo "frontend=${{ env.REGISTRY }}/${IMAGE_REPO}-frontend:${SHA_TAG}" >> $GITHUB_OUTPUT
echo "frontend_latest=${{ env.REGISTRY }}/${IMAGE_REPO}-frontend:latest" >> $GITHUB_OUTPUT
- name: Build and push backend
uses: docker/build-push-action@v6
with:
context: .
file: backend/Dockerfile
push: true
tags: |
${{ steps.tags.outputs.backend }}
${{ steps.tags.outputs.backend_latest }}
- name: Build and push frontend
uses: docker/build-push-action@v6
with:
context: frontend
file: frontend/Dockerfile
push: true
tags: |
${{ steps.tags.outputs.frontend }}
${{ steps.tags.outputs.frontend_latest }}
- name: Show image tags
run: |
echo "Backend: ${{ steps.tags.outputs.backend }}"
echo "Frontend: ${{ steps.tags.outputs.frontend }}"
deploy-compose:
runs-on: ubuntu-latest
needs: build-and-push
environment: production
concurrency:
group: prod-deploy
cancel-in-progress: false
steps:
- name: Select ref
run: |
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "REF=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV
else
echo "REF=${GITHUB_SHA}" >> $GITHUB_ENV
fi
- uses: actions/checkout@v4
with:
ref: ${{ env.REF }}
- name: Verify required secrets are present
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
for v in SSH_HOST SSH_USER SSH_PRIVATE_KEY; do
if [ -z "${!v}" ]; then echo "Missing secret: $v"; exit 1; fi
done
- name: Verify PR origin (internal only)
if: ${{ github.event_name == 'pull_request_target' }}
run: |
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
echo "Refusing to deploy from external fork PR"; exit 1; fi
- name: Upload docker-compose file
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "docker-compose.prod.yml"
target: "/opt/edgeflow/docker-compose.yml"
strip_components: 0
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
set -euo pipefail
APP_DIR=/opt/edgeflow
sudo mkdir -p ${APP_DIR}
cd ${APP_DIR}
# Write .env with image tags
cat > .env << 'EOF'
BACKEND_IMAGE=${{ needs.build-and-push.outputs.backend_image }}
FRONTEND_IMAGE=${{ needs.build-and-push.outputs.frontend_image }}
EOF
# Login to GHCR if credentials provided
if [ -n "${{ secrets.GHCR_USERNAME }}" ] && [ -n "${{ secrets.GHCR_TOKEN }}" ]; then
echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
fi
# Ensure docker compose is available
(docker compose version || docker-compose version)
# Pull and run
(docker compose --env-file .env -f docker-compose.yml pull || docker-compose --env-file .env -f docker-compose.yml pull)
(docker compose --env-file .env -f docker-compose.yml up -d || docker-compose --env-file .env -f docker-compose.yml up -d)
docker image prune -f || true
# Basic health checks
curl -fsS http://localhost:8000/api/health || (echo 'Backend health failed' && exit 1)
curl -fsS http://localhost:3000/ || (echo 'Frontend health failed' && exit 1)