-
Notifications
You must be signed in to change notification settings - Fork 6
148 lines (131 loc) · 5.34 KB
/
Copy pathcd.yml
File metadata and controls
148 lines (131 loc) · 5.34 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
name: CD
on:
push:
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 }}
steps:
- uses: actions/checkout@v4
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- 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
provenance: false
compression: gzip
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
provenance: false
compression: gzip
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:
- uses: actions/checkout@v4
- 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: |
if [ -z "$SSH_HOST" ] || [ -z "$SSH_USER" ]; then
echo "Missing SSH_HOST or SSH_USER"; exit 1; fi
if [ -z "$SSH_PRIVATE_KEY" ]; then
echo "SSH_PRIVATE_KEY is required (password auth not supported)"; 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 }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
source: "docker-compose.prod.yml"
target: "/tmp"
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 }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
script: |
set -euo pipefail
APP_DIR=/opt/edgeflow
sudo mkdir -p ${APP_DIR}
cd ${APP_DIR}
# Move compose file into place
if [ -d docker-compose.yml ]; then sudo rm -rf docker-compose.yml; fi
if [ -f /tmp/docker-compose.prod.yml ]; then mv /tmp/docker-compose.prod.yml docker-compose.yml; fi
# 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
export COMPOSE_PROJECT_NAME=edgeflow
# Stop any existing stack and containers to free ports (plugin only)
(docker compose -p ${COMPOSE_PROJECT_NAME} -f docker-compose.yml down -v || true)
(docker rm -f edgeflow-backend edgeflow-frontend || true)
(docker network rm ${COMPOSE_PROJECT_NAME}_edgeflow-net || true)
sleep 2
# Pull and run cleanly
docker compose -p ${COMPOSE_PROJECT_NAME} --env-file .env -f docker-compose.yml pull
docker compose -p ${COMPOSE_PROJECT_NAME} --env-file .env -f docker-compose.yml up -d --remove-orphans --force-recreate
docker image prune -f || true
# Basic health checks (backend on host port 18000)
curl -fsS http://localhost:18000/api/health || (echo 'Backend health failed' && exit 1)
curl -fsS http://localhost:13000/ || (echo 'Frontend health failed' && exit 1)