-
Notifications
You must be signed in to change notification settings - Fork 11
205 lines (194 loc) · 9.46 KB
/
deploy_docker.yml
File metadata and controls
205 lines (194 loc) · 9.46 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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# Deploy workflow using a "pull-before-restart" strategy to minimize downtime.
#
# How it works:
# 1. Copy config files to the VM while old containers are still running
# 2. Pre-pull new Docker images (this is the slow part, ~1-2 min)
# 3. Recreate containers with the already-cached images (near-instant, ~5-10s)
#
# This avoids the old approach of running "docker compose down" first, which
# caused 3-4 minutes of downtime while files were copied and images were pulled.
#
# Key details:
# - "docker compose up -d --force-recreate" stops and recreates all containers.
# --force-recreate ensures env-only changes (without image tag changes) are
# always picked up, since compose does not track .env file content changes.
# - "--remove-orphans" cleans up containers from services that were renamed or
# removed from the compose file.
# - "docker image prune -f" removes old unused images to prevent disk bloat.
name: Deploy Docker Image
on:
workflow_call:
inputs:
environment:
required: true
type: string
server_image_tag:
default: "latest"
type: string
client_image_tag:
default: "latest"
type: string
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ inputs.environment }}
url: '${{ vars.CLIENT_HOST }}'
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Create main.cf and master.cf File
env:
POSTFIX_MAIN_CF: ${{ vars.POSTFIX_MAIN_CF }}
POSTFIX_MASTER_CF: ${{ vars.POSTFIX_MASTER_CF }}
run: |
printf '%s' "$POSTFIX_MAIN_CF" > main.cf
printf '%s' "$POSTFIX_MASTER_CF" > master.cf
# All files are copied in a single SCP call to avoid multiple SSH handshakes
# through the bastion host. Files land in the home directory; the postfix
# configs are moved to their subdirectory in the next step.
- name: Copy Files to VM Host
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
source: "docker-compose.prod.yml,main.cf,master.cf"
target: /home/${{ vars.VM_USERNAME }}
- name: SSH to VM and Move Postfix Config Files
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
mkdir -p postfix-config
mv -f main.cf master.cf postfix-config/
- name: SSH to VM and Create .env.prod
uses: appleboy/ssh-action@v1.2.5
env:
SERVER_TAG: ${{ inputs.server_image_tag }}
CLIENT_TAG: ${{ inputs.client_image_tag }}
SPRING_DATASOURCE_DATABASE: ${{ vars.SPRING_DATASOURCE_DATABASE }}
SPRING_DATASOURCE_USERNAME: ${{ vars.SPRING_DATASOURCE_USERNAME }}
SPRING_DATASOURCE_PASSWORD: ${{ secrets.SPRING_DATASOURCE_PASSWORD }}
APP_HOSTNAME: ${{ vars.APP_HOSTNAME }}
SERVER_HOST: ${{ vars.SERVER_HOST }}
CLIENT_HOST: ${{ vars.CLIENT_HOST }}
APPLICATION_TITLE: ${{ vars.APPLICATION_TITLE }}
CHAIR_NAME: ${{ vars.CHAIR_NAME }}
CHAIR_URL: ${{ vars.CHAIR_URL }}
ALLOW_SUGGESTED_TOPICS: ${{ vars.ALLOW_SUGGESTED_TOPICS }}
THESIS_TYPES: ${{ vars.THESIS_TYPES }}
STUDY_PROGRAMS: ${{ vars.STUDY_PROGRAMS }}
STUDY_DEGREES: ${{ vars.STUDY_DEGREES }}
GENDERS: ${{ vars.GENDERS }}
LANGUAGES: ${{ vars.LANGUAGES }}
CUSTOM_DATA: ${{ vars.CUSTOM_DATA }}
THESIS_FILES: ${{ vars.THESIS_FILES }}
MAIL_SENDER: ${{ vars.MAIL_SENDER }}
KEYCLOAK_HOST: ${{ vars.KEYCLOAK_HOST }}
KEYCLOAK_REALM_NAME: ${{ vars.KEYCLOAK_REALM_NAME }}
KEYCLOAK_CLIENT_ID: ${{ vars.KEYCLOAK_CLIENT_ID }}
KEYCLOAK_SERVICE_CLIENT_ID: ${{ vars.KEYCLOAK_SERVICE_CLIENT_ID }}
KEYCLOAK_SERVICE_CLIENT_SECRET: ${{ secrets.KEYCLOAK_SERVICE_CLIENT_SECRET }}
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
envs: SERVER_TAG,CLIENT_TAG,SPRING_DATASOURCE_DATABASE,SPRING_DATASOURCE_USERNAME,SPRING_DATASOURCE_PASSWORD,APP_HOSTNAME,SERVER_HOST,CLIENT_HOST,APPLICATION_TITLE,CHAIR_NAME,CHAIR_URL,ALLOW_SUGGESTED_TOPICS,THESIS_TYPES,STUDY_PROGRAMS,STUDY_DEGREES,GENDERS,LANGUAGES,CUSTOM_DATA,THESIS_FILES,MAIL_SENDER,KEYCLOAK_HOST,KEYCLOAK_REALM_NAME,KEYCLOAK_CLIENT_ID,KEYCLOAK_SERVICE_CLIENT_ID,KEYCLOAK_SERVICE_CLIENT_SECRET
script: |
rm -f .env.prod
cat > .env.prod << ENVEOF
SPRING_DATASOURCE_DATABASE=${SPRING_DATASOURCE_DATABASE}
SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
APP_HOSTNAME=${APP_HOSTNAME}
SERVER_HOST=${SERVER_HOST}
CLIENT_HOST=${CLIENT_HOST}
APPLICATION_TITLE=${APPLICATION_TITLE}
CHAIR_NAME=${CHAIR_NAME}
CHAIR_URL=${CHAIR_URL}
ALLOW_SUGGESTED_TOPICS=${ALLOW_SUGGESTED_TOPICS}
THESIS_TYPES=${THESIS_TYPES}
STUDY_PROGRAMS=${STUDY_PROGRAMS}
STUDY_DEGREES=${STUDY_DEGREES}
GENDERS=${GENDERS}
LANGUAGES=${LANGUAGES}
CUSTOM_DATA=${CUSTOM_DATA}
THESIS_FILES=${THESIS_FILES}
MAIL_SENDER=${MAIL_SENDER}
KEYCLOAK_HOST=${KEYCLOAK_HOST}
KEYCLOAK_REALM_NAME=${KEYCLOAK_REALM_NAME}
KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID}
KEYCLOAK_SERVICE_CLIENT_ID=${KEYCLOAK_SERVICE_CLIENT_ID}
KEYCLOAK_SERVICE_CLIENT_SECRET=${KEYCLOAK_SERVICE_CLIENT_SECRET}
SERVER_IMAGE_TAG=${SERVER_TAG:-latest}
CLIENT_IMAGE_TAG=${CLIENT_TAG:-latest}
ENVEOF
sed -i 's/^[[:space:]]*//' .env.prod
# Pull new images while old containers are still running and serving traffic.
# This is the slow step (~1-2 min), but causes zero downtime since old
# containers remain up. Must run after .env.prod is created because compose
# needs it to resolve image tag variables (SERVER_IMAGE_TAG, CLIENT_IMAGE_TAG).
- name: SSH to VM and Pre-Pull New Images
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
docker compose -f docker-compose.prod.yml --env-file=.env.prod pull
# Recreate all containers with the pre-pulled images. Since images are already
# cached locally, this takes ~5-10 seconds instead of minutes.
# - --force-recreate: ensures containers are recreated even if only .env.prod
# changed (compose does not track env-file content changes on its own)
# - --remove-orphans: cleans up containers from services that were renamed or
# removed from the compose file
# No "docker compose down" is needed — "up -d" handles stopping old containers
# and starting new ones. Traefik detects the new containers automatically.
- name: SSH to VM and Execute Docker-Compose Up
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
docker compose -f docker-compose.prod.yml --env-file=.env.prod up -d --force-recreate --remove-orphans
# Remove old dangling images left behind after pulling new versions.
# Without this, each deployment leaves behind ~200-500MB of unused image
# layers that accumulate over time.
- name: SSH to VM and Cleanup Old Images
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
script: |
docker image prune -f