Skip to content

Commit 66f35fd

Browse files
authored
Merge pull request #305 from DevKor-github/codexd/dev-remote-pc-deploy-workflow
[codex] Add dev remote PC deploy workflow
2 parents 30400f0 + 6920b4f commit 66f35fd

5 files changed

Lines changed: 362 additions & 20 deletions

File tree

.github/workflows/deploy-dev.yml

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
name: Deploy Dev
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- dev
8+
9+
permissions:
10+
contents: read
11+
packages: write
12+
13+
concurrency:
14+
group: deploy-development
15+
cancel-in-progress: true
16+
17+
env:
18+
REGISTRY: ghcr.io
19+
IMAGE_NAME: devkor-github/ontime-back
20+
IMAGE_TAG: dev-${{ github.sha }}
21+
22+
jobs:
23+
build-and-push:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@v4
28+
29+
- name: Set up Docker Buildx
30+
uses: docker/setup-buildx-action@v3
31+
32+
- name: Log in to GHCR
33+
uses: docker/login-action@v3
34+
with:
35+
registry: ${{ env.REGISTRY }}
36+
username: ${{ github.actor }}
37+
password: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Build and push image
40+
uses: docker/build-push-action@v6
41+
with:
42+
context: ./ontime-back
43+
file: ./ontime-back/Dockerfile
44+
push: true
45+
tags: |
46+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
47+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev-latest
48+
cache-from: type=gha
49+
cache-to: type=gha,mode=max
50+
51+
deploy-to-remote-pc:
52+
needs: build-and-push
53+
runs-on: ubuntu-latest
54+
environment: development
55+
env:
56+
DEV_DEPLOY_DIR: ${{ secrets.DEV_DEPLOY_DIR || format('/home/{0}/OnTime-back-dev', secrets.DEV_REMOTE_USER) }}
57+
steps:
58+
- name: Checkout repository
59+
uses: actions/checkout@v4
60+
61+
- name: Prepare dev deploy directory
62+
uses: appleboy/ssh-action@v1.0.3
63+
with:
64+
host: ${{ secrets.DEV_REMOTE_HOST }}
65+
username: ${{ secrets.DEV_REMOTE_USER }}
66+
key: ${{ secrets.DEV_REMOTE_SSH_KEY }}
67+
script: |
68+
set -eu
69+
mkdir -p "${{ env.DEV_DEPLOY_DIR }}"
70+
71+
- name: Upload compose files to remote PC
72+
uses: appleboy/scp-action@v0.1.7
73+
with:
74+
host: ${{ secrets.DEV_REMOTE_HOST }}
75+
username: ${{ secrets.DEV_REMOTE_USER }}
76+
key: ${{ secrets.DEV_REMOTE_SSH_KEY }}
77+
source: "ontime-back/docker-compose.yml,ontime-back/docker-compose.dev.yml"
78+
target: ${{ env.DEV_DEPLOY_DIR }}
79+
strip_components: 1
80+
81+
- name: Pull image and restart dev containers
82+
uses: appleboy/ssh-action@v1.0.3
83+
with:
84+
host: ${{ secrets.DEV_REMOTE_HOST }}
85+
username: ${{ secrets.DEV_REMOTE_USER }}
86+
key: ${{ secrets.DEV_REMOTE_SSH_KEY }}
87+
script: |
88+
set -eu
89+
90+
DEPLOY_DIR="${{ env.DEV_DEPLOY_DIR }}"
91+
CONTAINER_NAME="ontime-dev-container"
92+
93+
mkdir -p "$DEPLOY_DIR"
94+
cd "$DEPLOY_DIR"
95+
96+
umask 077
97+
cat > .env <<'EOF'
98+
IMAGE_TAG=${{ env.IMAGE_TAG }}
99+
BACKEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
100+
BACKEND_CONTAINER_NAME=ontime-dev-container
101+
BACKEND_HTTP_PORT=${{ secrets.DEV_BACKEND_HTTP_PORT || '8081' }}
102+
BACKEND_MEMORY_LIMIT=${{ secrets.DEV_BACKEND_MEMORY_LIMIT || '768m' }}
103+
BACKEND_CPU_LIMIT=${{ secrets.DEV_BACKEND_CPU_LIMIT || '1.0' }}
104+
SERVER_PORT=8080
105+
SPRING_PROFILES_ACTIVE=dev
106+
JAVA_TOOL_OPTIONS=-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom
107+
108+
MYSQL_DATABASE=${{ secrets.DEV_MYSQL_DATABASE || 'ontime_dev' }}
109+
MYSQL_USER=${{ secrets.DEV_MYSQL_USER || 'ontime_dev' }}
110+
MYSQL_PASSWORD=${{ secrets.DEV_MYSQL_PASSWORD || 'ontime_dev_password' }}
111+
MYSQL_ROOT_PASSWORD=${{ secrets.DEV_MYSQL_ROOT_PASSWORD || 'ontime_dev_root_password' }}
112+
113+
SPRING_APPLICATION_NAME=${{ secrets.DEV_SPRING_APPLICATION_NAME || 'ontime-back-dev' }}
114+
SPRING_DATASOURCE_URL=${{ secrets.DEV_SPRING_DATASOURCE_URL || 'jdbc:mysql://mysql:3306/ontime_dev?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowPublicKeyRetrieval=true' }}
115+
SPRING_DATASOURCE_USERNAME=${{ secrets.DEV_SPRING_DATASOURCE_USERNAME || secrets.DEV_MYSQL_USER || 'ontime_dev' }}
116+
SPRING_DATASOURCE_PASSWORD=${{ secrets.DEV_SPRING_DATASOURCE_PASSWORD || secrets.DEV_MYSQL_PASSWORD || 'ontime_dev_password' }}
117+
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
118+
SPRING_JPA_HIBERNATE_DDL_AUTO=validate
119+
120+
SPRING_FLYWAY_ENABLED=true
121+
SPRING_FLYWAY_BASELINE_ON_MIGRATE=false
122+
123+
JWT_SECRET_KEY=${{ secrets.DEV_JWT_SECRETKEY || 'dev_secret_key_for_ontime_back_remote_pc_development_1234567890' }}
124+
JWT_ACCESS_EXPIRATION=${{ secrets.DEV_JWT_ACCESS_EXPIRATION || '3600000' }}
125+
JWT_REFRESH_EXPIRATION=${{ secrets.DEV_JWT_REFRESH_EXPIRATION || '1209600000' }}
126+
JWT_ACCESS_HEADER=${{ secrets.DEV_JWT_ACCESS_HEADER || 'Authorization' }}
127+
JWT_REFRESH_HEADER=${{ secrets.DEV_JWT_REFRESH_HEADER || 'Authorization-refresh' }}
128+
129+
GOOGLE_WEB_CLIENT_ID=${{ secrets.DEV_GOOGLE_WEB_CLIENT_ID || 'dev-google-web-client-id' }}
130+
GOOGLE_APP_CLIENT_ID=${{ secrets.DEV_GOOGLE_APP_CLIENT_ID || 'dev-google-app-client-id' }}
131+
132+
APPLE_CLIENT_ID=${{ secrets.DEV_APPLE_CLIENT_ID || 'dev-apple-client-id' }}
133+
APPLE_TEAM_ID=${{ secrets.DEV_APPLE_TEAM_ID || 'dev-apple-team-id' }}
134+
APPLE_LOGIN_KEY=${{ secrets.DEV_APPLE_LOGIN_KEY || 'dev-apple-key-id' }}
135+
APPLE_PRIVATE_KEY_BASE64=${{ secrets.DEV_APPLE_PRIVATE_KEY_BASE64 }}
136+
FEATURE_APPLE_LOGIN_ENABLED=${{ secrets.DEV_FEATURE_APPLE_LOGIN_ENABLED || 'false' }}
137+
138+
FIREBASE_CREDENTIALS_BASE64=${{ secrets.DEV_FIREBASE_CREDENTIALS_BASE64 }}
139+
EOF
140+
141+
echo "${{ secrets.GHCR_READ_TOKEN }}" | sudo docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
142+
143+
if sudo docker compose version >/dev/null 2>&1; then
144+
COMPOSE="sudo docker compose"
145+
else
146+
COMPOSE="sudo docker-compose"
147+
fi
148+
149+
$COMPOSE -f docker-compose.yml -f docker-compose.dev.yml pull
150+
$COMPOSE -f docker-compose.yml -f docker-compose.dev.yml up -d --remove-orphans
151+
152+
HEALTHY=false
153+
for attempt in $(seq 1 30); do
154+
STATUS="$(sudo docker inspect -f '{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || true)"
155+
if [ "$STATUS" = "healthy" ]; then
156+
echo "Container is healthy."
157+
HEALTHY=true
158+
break
159+
fi
160+
echo "Waiting for healthy container status; current status: ${STATUS:-unknown}"
161+
sleep 5
162+
done
163+
164+
if [ "$HEALTHY" != "true" ]; then
165+
sudo docker logs --tail=200 "$CONTAINER_NAME" || true
166+
exit 1
167+
fi
168+
169+
curl -fsS "http://127.0.0.1:${{ secrets.DEV_BACKEND_HTTP_PORT || '8081' }}/actuator/health/readiness"

docs/deployment.md

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
# Production Deployment
1+
# Deployment
22

3-
This service deploys as an immutable Docker image published to GitHub Container Registry (GHCR). Runtime configuration is injected through the EC2 `.env` file generated by GitHub Actions; private resource files are not copied into the image or bind-mounted from the host.
3+
This service deploys as an immutable Docker image published to GitHub Container Registry (GHCR). Runtime configuration is injected through `.env` files generated by GitHub Actions; private resource files are not copied into the image or bind-mounted from the host.
44

5-
## Required GitHub Secrets
5+
Production deploys to EC2 from `main`. Development deploys to a remote Ubuntu/Linux PC from `dev`.
6+
7+
## Production GitHub Secrets
68

79
Deployment access:
810

@@ -85,9 +87,7 @@ base64 -i ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8 | tr -d '\n'
8587

8688
Push to the `main` branch, or run `.github/workflows/deploy.yml` manually, to deploy production.
8789

88-
Pushes to `dev` run CI only. There is no dev-server deploy workflow in the one-EC2 plan.
89-
90-
The workflow:
90+
The production workflow:
9191

9292
1. Builds `ontime-back/Dockerfile` from the `ontime-back/` context.
9393
2. Pushes two GHCR tags:
@@ -99,6 +99,65 @@ The workflow:
9999
6. Runs `docker compose pull && docker compose up -d --remove-orphans`.
100100
7. Waits until the `ontime-container` Docker health status is `healthy`.
101101

102+
## Development Remote PC Deployment
103+
104+
Push to the `dev` branch, or run `.github/workflows/deploy-dev.yml` manually, to deploy the development backend to the remote PC.
105+
106+
The development workflow:
107+
108+
1. Builds `ontime-back/Dockerfile` from the `ontime-back/` context.
109+
2. Pushes two GHCR tags:
110+
- `ghcr.io/devkor-github/ontime-back:dev-<commit-sha>`
111+
- `ghcr.io/devkor-github/ontime-back:dev-latest`
112+
3. Uploads `docker-compose.yml` and `docker-compose.dev.yml` to the remote PC.
113+
4. Writes a development `.env` from GitHub secrets and safe dev defaults.
114+
5. Runs `docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --remove-orphans`.
115+
6. Starts MySQL as a private Docker Compose service with persistent volume `ontime-dev-mysql-data`.
116+
7. Waits until the `ontime-dev-container` Docker health status is `healthy`.
117+
118+
Required development secrets:
119+
120+
- `DEV_REMOTE_HOST`
121+
- `DEV_REMOTE_USER`
122+
- `DEV_REMOTE_SSH_KEY`
123+
- `GHCR_USERNAME`
124+
- `GHCR_READ_TOKEN`
125+
126+
Optional development secrets:
127+
128+
- `DEV_DEPLOY_DIR` (defaults to `/home/<DEV_REMOTE_USER>/OnTime-back-dev`)
129+
- `DEV_BACKEND_HTTP_PORT` (defaults to `8081`)
130+
- `DEV_BACKEND_MEMORY_LIMIT` (defaults to `768m`)
131+
- `DEV_BACKEND_CPU_LIMIT` (defaults to `1.0`)
132+
- `DEV_MYSQL_DATABASE` (defaults to `ontime_dev`)
133+
- `DEV_MYSQL_USER` (defaults to `ontime_dev`)
134+
- `DEV_MYSQL_PASSWORD` (defaults to `ontime_dev_password`)
135+
- `DEV_MYSQL_ROOT_PASSWORD` (defaults to `ontime_dev_root_password`)
136+
- `DEV_SPRING_APPLICATION_NAME` (defaults to `ontime-back-dev`)
137+
- `DEV_SPRING_DATASOURCE_URL` (defaults to the Compose MySQL service)
138+
- `DEV_SPRING_DATASOURCE_USERNAME` (defaults to the dev MySQL user)
139+
- `DEV_SPRING_DATASOURCE_PASSWORD` (defaults to the dev MySQL password)
140+
- `DEV_JWT_SECRETKEY`
141+
- `DEV_JWT_ACCESS_EXPIRATION`
142+
- `DEV_JWT_REFRESH_EXPIRATION`
143+
- `DEV_JWT_ACCESS_HEADER`
144+
- `DEV_JWT_REFRESH_HEADER`
145+
- `DEV_GOOGLE_WEB_CLIENT_ID`
146+
- `DEV_GOOGLE_APP_CLIENT_ID`
147+
- `DEV_APPLE_CLIENT_ID`
148+
- `DEV_APPLE_TEAM_ID`
149+
- `DEV_APPLE_LOGIN_KEY`
150+
- `DEV_APPLE_PRIVATE_KEY_BASE64`
151+
- `DEV_FEATURE_APPLE_LOGIN_ENABLED` (defaults to `false`)
152+
- `DEV_FIREBASE_CREDENTIALS_BASE64`
153+
154+
Remote PC prerequisites:
155+
156+
- Ubuntu/Linux host with normal SSH access from GitHub Actions.
157+
- Docker and the Docker Compose plugin installed.
158+
- Inbound firewall access for the backend HTTP port, default `8081`.
159+
- No public inbound MySQL port is required; MySQL stays inside the Docker network.
160+
102161
## Health Verification
103162

104163
The production image exposes a Docker healthcheck against:
@@ -117,6 +176,16 @@ curl -fsS http://localhost:8080/actuator/health/readiness
117176
nc -zv ontime-prod.cpoeguokwaq5.ap-northeast-2.rds.amazonaws.com 3306
118177
```
119178

179+
Manual checks on the remote development PC:
180+
181+
```bash
182+
cd /home/<DEV_REMOTE_USER>/OnTime-back-dev
183+
sudo docker compose -f docker-compose.yml -f docker-compose.dev.yml ps
184+
sudo docker inspect -f '{{.State.Health.Status}}' ontime-dev-container
185+
sudo docker logs --tail=200 ontime-dev-container
186+
curl -fsS http://<remote-pc-host>:8081/actuator/health/readiness
187+
```
188+
120189
## Rollback
121190

122191
Every deploy is tagged by commit SHA. To roll back, set `IMAGE_TAG` in `/home/ubuntu/OnTime-back/.env` to the previous known-good SHA, then restart from the existing Compose file:

0 commit comments

Comments
 (0)