From d1b9462b46486c784638e18cf0747fc8d8d14c64 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Fri, 8 May 2026 01:16:04 +0900 Subject: [PATCH 1/3] chore: align git workflow and deployments --- .github/workflows/deploy-dev.yml | 185 +++++++++++++++++++++++++ .github/workflows/deploy.yml | 4 +- .github/workflows/test.yml | 49 +------ docs/deployment.md | 12 +- docs/git-workflow.md | 226 +++++++++++++++++++++++++++++++ 5 files changed, 428 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 docs/git-workflow.md diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..7eca883 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,185 @@ +name: Deploy Dev + +on: + workflow_dispatch: + push: + branches: + - dev + +permissions: + contents: read + packages: write + +concurrency: + group: deploy-development + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + IMAGE_NAME: devkor-github/ontime-back + IMAGE_TAG: dev-${{ github.sha }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: ./ontime-back + file: ./ontime-back/Dockerfile + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev-latest + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy-to-dev-ec2: + needs: build-and-push + runs-on: ubuntu-latest + environment: development + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Upload compose file to dev EC2 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.DEV_EC2_HOST }} + username: ${{ secrets.DEV_EC2_USER }} + key: ${{ secrets.DEV_EC2_SSH_KEY }} + source: "ontime-back/docker-compose.yml" + target: "/home/ubuntu/OnTime-back-dev" + strip_components: 1 + + - name: Pull image and restart dev container + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.DEV_EC2_HOST }} + username: ${{ secrets.DEV_EC2_USER }} + key: ${{ secrets.DEV_EC2_SSH_KEY }} + script: | + set -eu + + DEPLOY_DIR="/home/ubuntu/OnTime-back-dev" + CONTAINER_NAME="ontime-dev-container" + + mkdir -p "$DEPLOY_DIR" + cd "$DEPLOY_DIR" + + umask 077 + cat > .env <<'EOF' + IMAGE_TAG=${{ env.IMAGE_TAG }} + BACKEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + BACKEND_CONTAINER_NAME=ontime-dev-container + BACKEND_HTTP_PORT=${{ secrets.DEV_BACKEND_HTTP_PORT || '8081' }} + SERVER_PORT=8080 + SPRING_PROFILES_ACTIVE=prod + JAVA_TOOL_OPTIONS=-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom + + SPRING_APPLICATION_NAME=${{ secrets.DEV_SPRING_APPLICATION_NAME }} + SPRING_DATASOURCE_URL=${{ secrets.DEV_SPRING_DATASOURCE_URL }} + SPRING_DATASOURCE_USERNAME=${{ secrets.DEV_SPRING_DATASOURCE_USERNAME }} + SPRING_DATASOURCE_PASSWORD=${{ secrets.DEV_SPRING_DATASOURCE_PASSWORD }} + SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver + SPRING_JPA_HIBERNATE_DDL_AUTO=validate + + SPRING_FLYWAY_ENABLED=true + SPRING_FLYWAY_BASELINE_ON_MIGRATE=false + + JWT_SECRET_KEY=${{ secrets.DEV_JWT_SECRETKEY }} + JWT_ACCESS_EXPIRATION=${{ secrets.DEV_JWT_ACCESS_EXPIRATION }} + JWT_REFRESH_EXPIRATION=${{ secrets.DEV_JWT_REFRESH_EXPIRATION }} + JWT_ACCESS_HEADER=${{ secrets.DEV_JWT_ACCESS_HEADER }} + JWT_REFRESH_HEADER=${{ secrets.DEV_JWT_REFRESH_HEADER }} + + GOOGLE_WEB_CLIENT_ID=${{ secrets.DEV_GOOGLE_WEB_CLIENT_ID }} + GOOGLE_APP_CLIENT_ID=${{ secrets.DEV_GOOGLE_APP_CLIENT_ID }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE }} + + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE }} + SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI }} + SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE }} + + APPLE_CLIENT_ID=${{ secrets.DEV_APPLE_CLIENT_ID }} + APPLE_TEAM_ID=${{ secrets.DEV_APPLE_TEAM_ID }} + APPLE_LOGIN_KEY=${{ secrets.DEV_APPLE_LOGIN_KEY }} + APPLE_PRIVATE_KEY_BASE64=${{ secrets.DEV_APPLE_PRIVATE_KEY_BASE64 }} + FEATURE_APPLE_LOGIN_ENABLED=${{ secrets.DEV_FEATURE_APPLE_LOGIN_ENABLED || 'true' }} + + FIREBASE_CREDENTIALS_BASE64=${{ secrets.DEV_FIREBASE_CREDENTIALS_BASE64 }} + EOF + + fail_deploy() { + echo "Unsafe development database configuration: $1" >&2 + exit 1 + } + + get_env_value() { + grep -E "^$1=" .env | tail -n 1 | cut -d= -f2- + } + + DB_URL="$(get_env_value SPRING_DATASOURCE_URL)" + DB_USERNAME="$(get_env_value SPRING_DATASOURCE_USERNAME)" + DB_PASSWORD="$(get_env_value SPRING_DATASOURCE_PASSWORD)" + DDL_AUTO="$(get_env_value SPRING_JPA_HIBERNATE_DDL_AUTO)" + FLYWAY_BASELINE="$(get_env_value SPRING_FLYWAY_BASELINE_ON_MIGRATE)" + NORMALIZED_DB_USERNAME="$(printf '%s' "$DB_USERNAME" | tr '[:upper:]' '[:lower:]')" + + [ -n "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL is required." + [ -n "$DB_USERNAME" ] || fail_deploy "SPRING_DATASOURCE_USERNAME is required." + [ -n "$DB_PASSWORD" ] || fail_deploy "SPRING_DATASOURCE_PASSWORD is required." + [ "$NORMALIZED_DB_USERNAME" != "root" ] || fail_deploy "SPRING_DATASOURCE_USERNAME must not be root." + [ "$DDL_AUTO" = "validate" ] || fail_deploy "SPRING_JPA_HIBERNATE_DDL_AUTO must be validate." + [ "$FLYWAY_BASELINE" = "false" ] || fail_deploy "SPRING_FLYWAY_BASELINE_ON_MIGRATE must be false." + + echo "${{ secrets.GHCR_READ_TOKEN }}" | sudo docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin + + if sudo docker compose version >/dev/null 2>&1; then + sudo docker compose pull + sudo docker compose up -d --remove-orphans + else + sudo docker-compose pull + sudo docker-compose up -d --remove-orphans + fi + + for attempt in $(seq 1 30); do + STATUS="$(sudo docker inspect -f '{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || true)" + if [ "$STATUS" = "healthy" ]; then + echo "Container is healthy." + exit 0 + fi + echo "Waiting for healthy container status; current status: ${STATUS:-unknown}" + sleep 5 + done + + sudo docker logs --tail=200 "$CONTAINER_NAME" || true + exit 1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7098287..8f0940d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,9 +1,10 @@ name: Deploy on: + workflow_dispatch: push: branches: - - deploy + - main permissions: contents: read @@ -50,6 +51,7 @@ jobs: deploy-to-ec2: needs: build-and-push runs-on: ubuntu-latest + environment: production steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2489e3..99ee7e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,16 +1,15 @@ -# PR이 올라왔을 때 자동으로 테스트코드를 실행하고 하나라도 Fail했을 시, PR을 Close하고 실패 코멘트를 달고 성공시에는 머지를 가능케하는 파이프라인. +# PR이 올라왔을 때 자동으로 테스트코드를 실행하고 실패 시 머지를 막는 파이프라인. name: PR Test on: pull_request: branches: - - main # main 브랜치로 머지되는 PR에서 실행 + - dev + - main jobs: test: runs-on: ubuntu-latest - outputs: - test_result: ${{ steps.run-tests.outcome }} # 테스트 결과를 output으로 저장 services: mysql: @@ -64,8 +63,6 @@ jobs: # 4. Gradle 빌드 & JUnit 테스트 실행 - name: Run Tests with Gradle - id: run-tests # 실행 결과를 output으로 저장할 id 추가 - continue-on-error: true env: SPRING_PROFILES_ACTIVE: test run: | @@ -76,43 +73,3 @@ jobs: run: | echo "Checking Flyway migration history..." mysql -h 127.0.0.1 -u test_user --password=test_password -e "SELECT * FROM test_db.flyway_schema_history;" - - - handle-failure: - needs: test - if: needs.test.outputs.test_result == 'failure' # test job의 output 값이 실패(failure)일 때 실행 - runs-on: ubuntu-latest - permissions: - pull-requests: write - issues: write - steps: - - name: Close PR - uses: octokit/request-action@v2.x - with: - route: PATCH /repos/{owner}/{repo}/pulls/{pull_number} - owner: ${{ github.repository_owner }} - repo: ${{ github.event.repository.name }} - pull_number: ${{ github.event.pull_request.number }} - state: closed - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Wait for PR to close - run: sleep 5 - - - name: Comment on PR - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const prNumber = context.payload.pull_request?.number || context.payload.issue?.number; - if (!prNumber) { - console.log("PR 번호를 찾을 수 없습니다."); - return; - } - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: "실패하는 테스트코드가 있어 PR이 자동으로 닫혔습니다.\nGithub Action에서 자세한 실패 로그를 확인하고 코드를 수정하세요." - }); diff --git a/docs/deployment.md b/docs/deployment.md index c8eb53d..ccd1bd5 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -65,6 +65,14 @@ Firebase: - `FIREBASE_CREDENTIALS_BASE64` +Development server: + +- `DEV_EC2_HOST` +- `DEV_EC2_USER` +- `DEV_EC2_SSH_KEY` +- `DEV_BACKEND_HTTP_PORT` (optional, defaults to `8081`) +- Runtime secrets matching the production names with a `DEV_` prefix, for example `DEV_SPRING_DATASOURCE_URL`, `DEV_JWT_SECRETKEY`, and `DEV_FIREBASE_CREDENTIALS_BASE64` + Set the base64 secrets from the ignored local credential files: ```bash @@ -77,7 +85,9 @@ base64 -i ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8 | tr -d '\n' ## Build And Release Flow -Push to the `deploy` branch to trigger `.github/workflows/deploy.yml`. +Push to the `main` branch, or run `.github/workflows/deploy.yml` manually, to deploy production. + +Push to the `dev` branch, or run `.github/workflows/deploy-dev.yml` manually, to deploy the development server. The workflow: diff --git a/docs/git-workflow.md b/docs/git-workflow.md new file mode 100644 index 0000000..d5b1c06 --- /dev/null +++ b/docs/git-workflow.md @@ -0,0 +1,226 @@ +# Git Workflow And Deployment Strategy + +This document describes the recommended Git strategy for OnTime-back when running both a development server and a production server. + +## Goals + +- Keep one clear production source of truth. +- Support a separate development server for integration testing. +- Make every server deployment traceable to a Git branch and commit. +- Avoid using deployment branches as places where product code diverges. +- Keep feature branches short-lived and easy to review. + +## Branch Model + +Use a lightweight environment-branch workflow: + +```text +feature/*, fix/*, chore/* -> short-lived work branches +dev -> development server source +main -> production server source +``` + +Branch responsibilities: + +| Branch | Purpose | Deployment | +| --- | --- | --- | +| `main` | Production-ready code and source of truth | Production server | +| `dev` | Integrated code for QA, frontend/mobile testing, and pre-release validation | Development server | +| `feature/*` | New feature work | No direct deployment | +| `fix/*` | Bug fixes | No direct deployment | +| `chore/*` | Maintenance, docs, config, CI changes | No direct deployment | + +Avoid keeping a long-lived `deploy` branch as the production source. Production should be represented by `main`, while deployment behavior should be handled by GitHub Actions environments and secrets. + +## Normal Development Workflow + +1. Start from the latest `dev`. + +```bash +git checkout dev +git pull origin dev +git checkout -b feat/some-feature +``` + +2. Commit focused changes on the feature branch. + +```bash +git add . +git commit -m "feat: add some feature" +``` + +3. Open a pull request into `dev`. + +```text +feature/* -> dev +``` + +4. CI runs tests and build checks. + +5. After review, merge into `dev`. + +6. GitHub Actions deploys the updated `dev` branch to the development server. + +7. Validate the change using the development server. + +8. When the release candidate is ready, open a pull request from `dev` into `main`. + +```text +dev -> main +``` + +9. After review and CI pass, merge into `main`. + +10. GitHub Actions deploys `main` to the production server. + +11. Tag the production release. + +```bash +git tag prod-YYYY-MM-DD +git push origin prod-YYYY-MM-DD +``` + +## Server Mapping + +Use branch-based deployments with separate GitHub Actions environments: + +```text +push to dev -> development environment -> dev server +push to main -> production environment -> production server +``` + +Recommended GitHub environments: + +| Environment | Source Branch | Server | Approval | +| --- | --- | --- | --- | +| `development` | `dev` | Dev server | Usually automatic | +| `production` | `main` | Production server | Manual approval recommended | + +## CI/CD Workflow + +Use either two workflows or one branch-aware workflow. + +Recommended simple setup: + +```text +.github/workflows/test.yml +.github/workflows/deploy-dev.yml +.github/workflows/deploy-prod.yml +``` + +Expected triggers: + +```text +pull_request to dev -> run tests +pull_request to main -> run tests +push to dev -> deploy to dev server +push to main -> deploy to production server +workflow_dispatch -> allow manual redeploy or rollback support +``` + +Development deploy should use development secrets only: + +```text +DEV_EC2_HOST +DEV_EC2_USER +DEV_EC2_SSH_KEY +DEV_DATASOURCE_URL +DEV_DATASOURCE_USERNAME +DEV_DATASOURCE_PASSWORD +``` + +Production deploy should use production secrets only: + +```text +PROD_EC2_HOST +PROD_EC2_USER +PROD_EC2_SSH_KEY +PROD_DATASOURCE_URL +PROD_DATASOURCE_USERNAME +PROD_DATASOURCE_PASSWORD +``` + +Do not share databases, Firebase credentials, OAuth redirect URIs, or private keys between development and production unless there is a deliberate reason. + +## Branch Protection + +Protect `dev`: + +- Require pull requests before merging. +- Require CI tests to pass. +- Allow squash merge. +- Allow faster review for low-risk changes. + +Protect `main` more strictly: + +- Require pull requests before merging. +- Require CI tests to pass. +- Require review approval. +- Require the PR branch to be up to date before merge. +- Block force pushes. +- Block direct pushes. +- Require production environment approval before deployment. + +## Release And Rollback + +Every production deployment should be traceable by commit SHA and tag. + +Recommended release tags: + +```text +prod-2026-05-07 +prod-2026-05-07-1 +v1.3.0 +``` + +Rollback options: + +1. Redeploy a previous image tag by commit SHA. +2. Revert the bad commit on `main` and deploy the revert. +3. If the issue only exists in configuration, update production secrets or environment config and rerun deployment. + +Prefer image-SHA rollback for urgent incidents, then follow up with a Git revert or fix PR so `main` still reflects production truth. + +## Migration From Current State + +The current repository has `main` and `deploy` as separate long-lived branches. The target state should be: + +```text +deploy branch -> retired +main branch -> production +dev branch -> development server +``` + +Recommended migration sequence: + +1. Compare `deploy` and `main`. +2. Merge or cherry-pick all intended production changes from `deploy` back into `main`. +3. Confirm `main` builds and tests successfully. +4. Create `dev` from the updated `main`. + +```bash +git checkout main +git pull origin main +git checkout -b dev +git push origin dev +``` + +5. Change production deployment to trigger from `main`. +6. Add development deployment to trigger from `dev`. +7. Update GitHub environment secrets for `development` and `production`. +8. Protect `main` and `dev`. +9. Stop using `deploy` for new work. +10. Delete or archive stale merged feature branches after confirming they are no longer needed. + +## Practical Rules + +- New work branches from `dev`. +- Normal PR target is `dev`. +- Release PR target is `main`. +- Production deploys only from `main`. +- Development deploys only from `dev`. +- Do not commit directly to `main`. +- Do not commit directly to `dev` unless it is an emergency coordination fix. +- Delete feature branches after merge. +- Keep database migrations forward-only. +- Keep environment-specific values in GitHub secrets, not committed files. From 1e156ba73793e601e91c78f14a9468904bf3e812 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Fri, 8 May 2026 02:56:05 +0900 Subject: [PATCH 2/3] Align deploy workflow with private RDS --- .github/workflows/deploy.yml | 33 ++++++++++++++++++++++++++++-- docs/deployment.md | 26 +++++++++++++++-------- ontime-back/docker-compose.yml | 5 +++++ ontime-back/docs/deployment/ec2.md | 27 ++++++++++++------------ 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7098287..bc6043b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,6 +1,7 @@ name: Deploy on: + workflow_dispatch: push: branches: - deploy @@ -85,6 +86,8 @@ jobs: BACKEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} BACKEND_CONTAINER_NAME=ontime-container BACKEND_HTTP_PORT=${{ secrets.BACKEND_HTTP_PORT || '8080' }} + BACKEND_MEMORY_LIMIT=${{ secrets.BACKEND_MEMORY_LIMIT || '768m' }} + BACKEND_CPU_LIMIT=${{ secrets.BACKEND_CPU_LIMIT || '1.0' }} SERVER_PORT=8080 SPRING_PROFILES_ACTIVE=prod JAVA_TOOL_OPTIONS=-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom @@ -152,14 +155,33 @@ jobs: FLYWAY_BASELINE="$(get_env_value SPRING_FLYWAY_BASELINE_ON_MIGRATE)" NORMALIZED_DB_URL="$(printf '%s' "$DB_URL" | tr '[:upper:]' '[:lower:]')" NORMALIZED_DB_USERNAME="$(printf '%s' "$DB_USERNAME" | tr '[:upper:]' '[:lower:]')" + DB_URL_NO_PREFIX="${DB_URL#jdbc:mysql://}" + [ "$DB_URL_NO_PREFIX" != "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL must start with jdbc:mysql://." + DB_ADDRESS="${DB_URL_NO_PREFIX%%/*}" + DB_HOST="${DB_ADDRESS%%:*}" + DB_PORT="${DB_ADDRESS#*:}" + [ "$DB_PORT" != "$DB_ADDRESS" ] || DB_PORT="3306" + DB_NAME_AND_QUERY="${DB_URL_NO_PREFIX#*/}" + DB_NAME="$(printf '%s' "$DB_NAME_AND_QUERY" | sed 's/[?;].*$//')" [ -n "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL is required." [ -n "$DB_USERNAME" ] || fail_deploy "SPRING_DATASOURCE_USERNAME is required." [ -n "$DB_PASSWORD" ] || fail_deploy "SPRING_DATASOURCE_PASSWORD is required." + [ -n "$DB_HOST" ] || fail_deploy "SPRING_DATASOURCE_URL must include an RDS host." + [ "$DB_NAME" = "ontime_prod" ] || fail_deploy "SPRING_DATASOURCE_URL must use the ontime_prod database." [ "$NORMALIZED_DB_USERNAME" != "root" ] || fail_deploy "SPRING_DATASOURCE_USERNAME must not be root." [ "$DDL_AUTO" = "validate" ] || fail_deploy "SPRING_JPA_HIBERNATE_DDL_AUTO must be validate." [ "$FLYWAY_BASELINE" = "false" ] || fail_deploy "SPRING_FLYWAY_BASELINE_ON_MIGRATE must be false." + case "$NORMALIZED_DB_URL" in + *localhost*|*127.0.0.1*|*host.docker.internal*) fail_deploy "SPRING_DATASOURCE_URL must point to private RDS, not a local database." ;; + esac + + case "$(printf '%s' "$DB_HOST" | tr '[:upper:]' '[:lower:]')" in + *.rds.amazonaws.com) ;; + *) fail_deploy "SPRING_DATASOURCE_URL host must be an RDS endpoint." ;; + esac + case "$NORMALIZED_DB_URL" in *allowpublickeyretrieval=true*) fail_deploy "SPRING_DATASOURCE_URL must not enable allowPublicKeyRetrieval." ;; esac @@ -173,10 +195,17 @@ jobs: esac case "$NORMALIZED_DB_URL" in - *sslmode=required*|*sslmode=verify_ca*|*sslmode=verify_identity*) ;; - *) fail_deploy "SPRING_DATASOURCE_URL must declare sslMode=REQUIRED, VERIFY_CA, or VERIFY_IDENTITY." ;; + *sslmode=required*|*sslmode=verify_ca*|*sslmode=verify_identity*|*usessl=true*) ;; + *) fail_deploy "SPRING_DATASOURCE_URL must declare useSSL=true, sslMode=REQUIRED, VERIFY_CA, or VERIFY_IDENTITY." ;; esac + echo "Checking EC2-to-RDS TCP connectivity for $DB_HOST:$DB_PORT..." + if command -v nc >/dev/null 2>&1; then + nc -zv "$DB_HOST" "$DB_PORT" + else + timeout 5 bash -c "/dev/null 2>&1; then diff --git a/docs/deployment.md b/docs/deployment.md index c8eb53d..15e39d6 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -15,18 +15,24 @@ Deployment access: Runtime image and port: - `BACKEND_HTTP_PORT` (optional, defaults to `8080`) +- `BACKEND_MEMORY_LIMIT` (optional, defaults to `768m`; use `640m` if the EC2 instance is memory constrained) +- `BACKEND_CPU_LIMIT` (optional, defaults to `1.0`) Spring and database: - `SPRING_APPLICATION_NAME` -- `SPRING_DATASOURCE_URL` -- `SPRING_DATASOURCE_USERNAME` +- `SPRING_DATASOURCE_URL` (`jdbc:mysql://ontime-prod.cpoeguokwaq5.ap-northeast-2.rds.amazonaws.com:3306/ontime_prod?useSSL=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8`) +- `SPRING_DATASOURCE_USERNAME` (`ontimeadmin`) - `SPRING_DATASOURCE_PASSWORD` -- `SPRING_DATASOURCE_DRIVER_CLASS_NAME` -- `SPRING_JPA_HIBERNATE_DDL_AUTO` -- `SPRING_FLYWAY_URL` -- `SPRING_FLYWAY_USER` -- `SPRING_FLYWAY_PASSWORD` + +The deploy workflow hardcodes safe production defaults for the datasource driver, JPA DDL mode, and Flyway: + +- `SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver` +- `SPRING_JPA_HIBERNATE_DDL_AUTO=validate` +- `SPRING_FLYWAY_ENABLED=true` +- `SPRING_FLYWAY_BASELINE_ON_MIGRATE=false` + +It also fails before restart if the datasource URL does not point to an RDS MySQL endpoint using the `ontime_prod` database, or if EC2 cannot reach RDS on `3306`. Authentication and OAuth: @@ -87,8 +93,9 @@ The workflow: - `ghcr.io/devkor-github/ontime-back:deploy-latest` 3. Uploads `docker-compose.yml` to `/home/ubuntu/OnTime-back`. 4. Writes `/home/ubuntu/OnTime-back/.env` from GitHub secrets. -5. Runs `docker compose pull && docker compose up -d --remove-orphans`. -6. Waits until the `ontime-container` Docker health status is `healthy`. +5. Verifies EC2 can reach private RDS on `3306`. +6. Runs `docker compose pull && docker compose up -d --remove-orphans`. +7. Waits until the `ontime-container` Docker health status is `healthy`. ## Health Verification @@ -105,6 +112,7 @@ cd /home/ubuntu/OnTime-back sudo docker compose ps sudo docker inspect -f '{{.State.Health.Status}}' ontime-container curl -fsS http://localhost:8080/actuator/health/readiness +nc -zv ontime-prod.cpoeguokwaq5.ap-northeast-2.rds.amazonaws.com 3306 ``` ## Rollback diff --git a/ontime-back/docker-compose.yml b/ontime-back/docker-compose.yml index e21529d..f336b47 100644 --- a/ontime-back/docker-compose.yml +++ b/ontime-back/docker-compose.yml @@ -14,6 +14,11 @@ services: stop_grace_period: 30s mem_limit: "${BACKEND_MEMORY_LIMIT:-768m}" cpus: "${BACKEND_CPU_LIMIT:-1.0}" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER_PORT:-8080}/actuator/health/readiness || exit 1"] interval: 30s diff --git a/ontime-back/docs/deployment/ec2.md b/ontime-back/docs/deployment/ec2.md index c1a0759..df6ed5b 100644 --- a/ontime-back/docs/deployment/ec2.md +++ b/ontime-back/docs/deployment/ec2.md @@ -8,7 +8,7 @@ This service deploys to Amazon EC2 through `.github/workflows/deploy.yml`. 2. Add the required GitHub Actions secrets listed below. 3. Run the `Deploy` workflow manually from GitHub Actions, or push to the `deploy` branch. -The workflow builds the Spring Boot jar, creates deploy-only config files from GitHub Secrets, uploads them to `/home/ubuntu/OnTime-back`, and restarts Docker Compose on the EC2 instance. +The workflow builds a Docker image, pushes it to GHCR, uploads `docker-compose.yml` to `/home/ubuntu/OnTime-back`, writes a production `.env` from GitHub Secrets, verifies private RDS connectivity, and restarts Docker Compose on the EC2 instance. ## Required EC2 Secrets @@ -22,8 +22,6 @@ The workflow builds the Spring Boot jar, creates deploy-only config files from G - `SPRING_DATASOURCE_URL` - `SPRING_DATASOURCE_USERNAME` - `SPRING_DATASOURCE_PASSWORD` -- `SPRING_DATASOURCE_DRIVER_CLASS_NAME` -- `SPRING_JPA_HIBERNATE_DDL_AUTO` - `JWT_SECRETKEY` - `JWT_ACCESS_EXPIRATION` - `JWT_REFRESH_EXPIRATION` @@ -34,15 +32,14 @@ The workflow builds the Spring Boot jar, creates deploy-only config files from G - `APPLE_CLIENT_ID` - `APPLE_LOGIN_KEY` - `APPLE_TEAM_ID` -- `AUTHKEY_743M7R5W3W` -- `SPRING_FLYWAY_URL` -- `SPRING_FLYWAY_USER` -- `SPRING_FLYWAY_PASSWORD` -- `ONTIME_PUSH_FIREBASE_ADMINSDK` +- `APPLE_PRIVATE_KEY_BASE64` +- `FIREBASE_CREDENTIALS_BASE64` ## Optional Secrets -- `SPRING_JPA_DATABASE_PLATFORM` defaults to `org.hibernate.dialect.MySQL8Dialect`. +- `BACKEND_HTTP_PORT` defaults to `8080`. +- `BACKEND_MEMORY_LIMIT` defaults to `768m`; use `640m` if the EC2 instance is memory constrained. +- `BACKEND_CPU_LIMIT` defaults to `1.0`. - `FEATURE_APPLE_LOGIN_ENABLED` defaults to `true`. - Google and Kakao OAuth provider/registration secrets are included by the workflow when configured. @@ -50,11 +47,13 @@ The workflow builds the Spring Boot jar, creates deploy-only config files from G The deploy workflow writes these files under `/home/ubuntu/OnTime-back`: -- `project.jar` -- `Dockerfile` - `docker-compose.yml` -- `config/application.properties` -- `secrets/firebase-adminsdk.json` -- `secrets/AuthKey_743M7R5W3W.p8` +- `.env` + +Production uses the private RDS instance: + +```text +ontime-prod.cpoeguokwaq5.ap-northeast-2.rds.amazonaws.com:3306/ontime_prod +``` Do not commit local `application.properties`, Firebase service account JSON, Apple `.p8` keys, or `.env` files. From 64bfb6fb569fe09c9ffa5e413fa22effa517ac5c Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Fri, 8 May 2026 03:10:57 +0900 Subject: [PATCH 3/3] Remove dev server deployment workflow --- .github/workflows/deploy-dev.yml | 185 ----------------------------- docs/deployment.md | 10 +- docs/git-workflow.md | 67 ++++------- ontime-back/docs/deployment/ec2.md | 2 +- 4 files changed, 27 insertions(+), 237 deletions(-) delete mode 100644 .github/workflows/deploy-dev.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml deleted file mode 100644 index 7eca883..0000000 --- a/.github/workflows/deploy-dev.yml +++ /dev/null @@ -1,185 +0,0 @@ -name: Deploy Dev - -on: - workflow_dispatch: - push: - branches: - - dev - -permissions: - contents: read - packages: write - -concurrency: - group: deploy-development - cancel-in-progress: true - -env: - REGISTRY: ghcr.io - IMAGE_NAME: devkor-github/ontime-back - IMAGE_TAG: dev-${{ github.sha }} - -jobs: - build-and-push: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push image - uses: docker/build-push-action@v6 - with: - context: ./ontime-back - file: ./ontime-back/Dockerfile - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev-latest - cache-from: type=gha - cache-to: type=gha,mode=max - - deploy-to-dev-ec2: - needs: build-and-push - runs-on: ubuntu-latest - environment: development - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Upload compose file to dev EC2 - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.DEV_EC2_HOST }} - username: ${{ secrets.DEV_EC2_USER }} - key: ${{ secrets.DEV_EC2_SSH_KEY }} - source: "ontime-back/docker-compose.yml" - target: "/home/ubuntu/OnTime-back-dev" - strip_components: 1 - - - name: Pull image and restart dev container - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.DEV_EC2_HOST }} - username: ${{ secrets.DEV_EC2_USER }} - key: ${{ secrets.DEV_EC2_SSH_KEY }} - script: | - set -eu - - DEPLOY_DIR="/home/ubuntu/OnTime-back-dev" - CONTAINER_NAME="ontime-dev-container" - - mkdir -p "$DEPLOY_DIR" - cd "$DEPLOY_DIR" - - umask 077 - cat > .env <<'EOF' - IMAGE_TAG=${{ env.IMAGE_TAG }} - BACKEND_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - BACKEND_CONTAINER_NAME=ontime-dev-container - BACKEND_HTTP_PORT=${{ secrets.DEV_BACKEND_HTTP_PORT || '8081' }} - SERVER_PORT=8080 - SPRING_PROFILES_ACTIVE=prod - JAVA_TOOL_OPTIONS=-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom - - SPRING_APPLICATION_NAME=${{ secrets.DEV_SPRING_APPLICATION_NAME }} - SPRING_DATASOURCE_URL=${{ secrets.DEV_SPRING_DATASOURCE_URL }} - SPRING_DATASOURCE_USERNAME=${{ secrets.DEV_SPRING_DATASOURCE_USERNAME }} - SPRING_DATASOURCE_PASSWORD=${{ secrets.DEV_SPRING_DATASOURCE_PASSWORD }} - SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver - SPRING_JPA_HIBERNATE_DDL_AUTO=validate - - SPRING_FLYWAY_ENABLED=true - SPRING_FLYWAY_BASELINE_ON_MIGRATE=false - - JWT_SECRET_KEY=${{ secrets.DEV_JWT_SECRETKEY }} - JWT_ACCESS_EXPIRATION=${{ secrets.DEV_JWT_ACCESS_EXPIRATION }} - JWT_REFRESH_EXPIRATION=${{ secrets.DEV_JWT_REFRESH_EXPIRATION }} - JWT_ACCESS_HEADER=${{ secrets.DEV_JWT_ACCESS_HEADER }} - JWT_REFRESH_HEADER=${{ secrets.DEV_JWT_REFRESH_HEADER }} - - GOOGLE_WEB_CLIENT_ID=${{ secrets.DEV_GOOGLE_WEB_CLIENT_ID }} - GOOGLE_APP_CLIENT_ID=${{ secrets.DEV_GOOGLE_APP_CLIENT_ID }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE }} - - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE }} - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI }} - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE=${{ secrets.DEV_SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE }} - - APPLE_CLIENT_ID=${{ secrets.DEV_APPLE_CLIENT_ID }} - APPLE_TEAM_ID=${{ secrets.DEV_APPLE_TEAM_ID }} - APPLE_LOGIN_KEY=${{ secrets.DEV_APPLE_LOGIN_KEY }} - APPLE_PRIVATE_KEY_BASE64=${{ secrets.DEV_APPLE_PRIVATE_KEY_BASE64 }} - FEATURE_APPLE_LOGIN_ENABLED=${{ secrets.DEV_FEATURE_APPLE_LOGIN_ENABLED || 'true' }} - - FIREBASE_CREDENTIALS_BASE64=${{ secrets.DEV_FIREBASE_CREDENTIALS_BASE64 }} - EOF - - fail_deploy() { - echo "Unsafe development database configuration: $1" >&2 - exit 1 - } - - get_env_value() { - grep -E "^$1=" .env | tail -n 1 | cut -d= -f2- - } - - DB_URL="$(get_env_value SPRING_DATASOURCE_URL)" - DB_USERNAME="$(get_env_value SPRING_DATASOURCE_USERNAME)" - DB_PASSWORD="$(get_env_value SPRING_DATASOURCE_PASSWORD)" - DDL_AUTO="$(get_env_value SPRING_JPA_HIBERNATE_DDL_AUTO)" - FLYWAY_BASELINE="$(get_env_value SPRING_FLYWAY_BASELINE_ON_MIGRATE)" - NORMALIZED_DB_USERNAME="$(printf '%s' "$DB_USERNAME" | tr '[:upper:]' '[:lower:]')" - - [ -n "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL is required." - [ -n "$DB_USERNAME" ] || fail_deploy "SPRING_DATASOURCE_USERNAME is required." - [ -n "$DB_PASSWORD" ] || fail_deploy "SPRING_DATASOURCE_PASSWORD is required." - [ "$NORMALIZED_DB_USERNAME" != "root" ] || fail_deploy "SPRING_DATASOURCE_USERNAME must not be root." - [ "$DDL_AUTO" = "validate" ] || fail_deploy "SPRING_JPA_HIBERNATE_DDL_AUTO must be validate." - [ "$FLYWAY_BASELINE" = "false" ] || fail_deploy "SPRING_FLYWAY_BASELINE_ON_MIGRATE must be false." - - echo "${{ secrets.GHCR_READ_TOKEN }}" | sudo docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin - - if sudo docker compose version >/dev/null 2>&1; then - sudo docker compose pull - sudo docker compose up -d --remove-orphans - else - sudo docker-compose pull - sudo docker-compose up -d --remove-orphans - fi - - for attempt in $(seq 1 30); do - STATUS="$(sudo docker inspect -f '{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || true)" - if [ "$STATUS" = "healthy" ]; then - echo "Container is healthy." - exit 0 - fi - echo "Waiting for healthy container status; current status: ${STATUS:-unknown}" - sleep 5 - done - - sudo docker logs --tail=200 "$CONTAINER_NAME" || true - exit 1 diff --git a/docs/deployment.md b/docs/deployment.md index 4ee20dd..a34dcf2 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -71,14 +71,6 @@ Firebase: - `FIREBASE_CREDENTIALS_BASE64` -Development server: - -- `DEV_EC2_HOST` -- `DEV_EC2_USER` -- `DEV_EC2_SSH_KEY` -- `DEV_BACKEND_HTTP_PORT` (optional, defaults to `8081`) -- Runtime secrets matching the production names with a `DEV_` prefix, for example `DEV_SPRING_DATASOURCE_URL`, `DEV_JWT_SECRETKEY`, and `DEV_FIREBASE_CREDENTIALS_BASE64` - Set the base64 secrets from the ignored local credential files: ```bash @@ -93,7 +85,7 @@ base64 -i ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8 | tr -d '\n' Push to the `main` branch, or run `.github/workflows/deploy.yml` manually, to deploy production. -Push to the `dev` branch, or run `.github/workflows/deploy-dev.yml` manually, to deploy the development server. +Pushes to `dev` run CI only. There is no dev-server deploy workflow in the one-EC2 plan. The workflow: diff --git a/docs/git-workflow.md b/docs/git-workflow.md index d5b1c06..1a0c6d4 100644 --- a/docs/git-workflow.md +++ b/docs/git-workflow.md @@ -1,14 +1,14 @@ -# Git Workflow And Deployment Strategy +# Git Workflow And Production Deployment Strategy -This document describes the recommended Git strategy for OnTime-back when running both a development server and a production server. +This document describes the recommended Git strategy for OnTime-back with one production EC2 server and one private production RDS instance. ## Goals - Keep one clear production source of truth. -- Support a separate development server for integration testing. - Make every server deployment traceable to a Git branch and commit. - Avoid using deployment branches as places where product code diverges. - Keep feature branches short-lived and easy to review. +- Keep `dev` as an integration branch only; it must not deploy a long-running dev backend. ## Branch Model @@ -16,7 +16,7 @@ Use a lightweight environment-branch workflow: ```text feature/*, fix/*, chore/* -> short-lived work branches -dev -> development server source +dev -> integration branch for CI and review main -> production server source ``` @@ -25,7 +25,7 @@ Branch responsibilities: | Branch | Purpose | Deployment | | --- | --- | --- | | `main` | Production-ready code and source of truth | Production server | -| `dev` | Integrated code for QA, frontend/mobile testing, and pre-release validation | Development server | +| `dev` | Integrated code for QA, frontend/mobile testing, and pre-release validation | No direct deployment | | `feature/*` | New feature work | No direct deployment | | `fix/*` | Bug fixes | No direct deployment | | `chore/*` | Maintenance, docs, config, CI changes | No direct deployment | @@ -59,21 +59,19 @@ feature/* -> dev 5. After review, merge into `dev`. -6. GitHub Actions deploys the updated `dev` branch to the development server. +6. Validate the integrated code without running a long-lived dev backend on EC2. -7. Validate the change using the development server. - -8. When the release candidate is ready, open a pull request from `dev` into `main`. +7. When the release candidate is ready, open a pull request from `dev` into `main`. ```text dev -> main ``` -9. After review and CI pass, merge into `main`. +8. After review and CI pass, merge into `main`. -10. GitHub Actions deploys `main` to the production server. +9. GitHub Actions deploys `main` to the production server. -11. Tag the production release. +10. Tag the production release. ```bash git tag prod-YYYY-MM-DD @@ -82,10 +80,10 @@ git push origin prod-YYYY-MM-DD ## Server Mapping -Use branch-based deployments with separate GitHub Actions environments: +Use branch-based CI and production deployment: ```text -push to dev -> development environment -> dev server +pull_request to dev/main -> test workflow push to main -> production environment -> production server ``` @@ -93,19 +91,15 @@ Recommended GitHub environments: | Environment | Source Branch | Server | Approval | | --- | --- | --- | --- | -| `development` | `dev` | Dev server | Usually automatic | | `production` | `main` | Production server | Manual approval recommended | ## CI/CD Workflow -Use either two workflows or one branch-aware workflow. - Recommended simple setup: ```text .github/workflows/test.yml -.github/workflows/deploy-dev.yml -.github/workflows/deploy-prod.yml +.github/workflows/deploy.yml ``` Expected triggers: @@ -113,34 +107,23 @@ Expected triggers: ```text pull_request to dev -> run tests pull_request to main -> run tests -push to dev -> deploy to dev server +push to dev -> no deployment push to main -> deploy to production server workflow_dispatch -> allow manual redeploy or rollback support ``` -Development deploy should use development secrets only: - -```text -DEV_EC2_HOST -DEV_EC2_USER -DEV_EC2_SSH_KEY -DEV_DATASOURCE_URL -DEV_DATASOURCE_USERNAME -DEV_DATASOURCE_PASSWORD -``` - Production deploy should use production secrets only: ```text -PROD_EC2_HOST -PROD_EC2_USER -PROD_EC2_SSH_KEY -PROD_DATASOURCE_URL -PROD_DATASOURCE_USERNAME -PROD_DATASOURCE_PASSWORD +EC2_HOST +EC2_USER +EC2_SSH_KEY +SPRING_DATASOURCE_URL +SPRING_DATASOURCE_USERNAME +SPRING_DATASOURCE_PASSWORD ``` -Do not share databases, Firebase credentials, OAuth redirect URIs, or private keys between development and production unless there is a deliberate reason. +Do not add `DEV_*` deployment secrets or a dev-server workflow unless the infrastructure plan changes deliberately. ## Branch Protection @@ -188,7 +171,7 @@ The current repository has `main` and `deploy` as separate long-lived branches. ```text deploy branch -> retired main branch -> production -dev branch -> development server +dev branch -> integration and CI only ``` Recommended migration sequence: @@ -206,8 +189,8 @@ git push origin dev ``` 5. Change production deployment to trigger from `main`. -6. Add development deployment to trigger from `dev`. -7. Update GitHub environment secrets for `development` and `production`. +6. Ensure there is no workflow that deploys from `dev`. +7. Update GitHub production environment secrets. 8. Protect `main` and `dev`. 9. Stop using `deploy` for new work. 10. Delete or archive stale merged feature branches after confirming they are no longer needed. @@ -218,7 +201,7 @@ git push origin dev - Normal PR target is `dev`. - Release PR target is `main`. - Production deploys only from `main`. -- Development deploys only from `dev`. +- `dev` runs CI only and does not deploy. - Do not commit directly to `main`. - Do not commit directly to `dev` unless it is an emergency coordination fix. - Delete feature branches after merge. diff --git a/ontime-back/docs/deployment/ec2.md b/ontime-back/docs/deployment/ec2.md index df6ed5b..1ae690e 100644 --- a/ontime-back/docs/deployment/ec2.md +++ b/ontime-back/docs/deployment/ec2.md @@ -6,7 +6,7 @@ This service deploys to Amazon EC2 through `.github/workflows/deploy.yml`. 1. Make sure the EC2 instance has Docker installed and the security group allows inbound traffic for the service port, currently `8080`. 2. Add the required GitHub Actions secrets listed below. -3. Run the `Deploy` workflow manually from GitHub Actions, or push to the `deploy` branch. +3. Run the `Deploy` workflow manually from GitHub Actions, or push to the `main` branch. The workflow builds a Docker image, pushes it to GHCR, uploads `docker-compose.yml` to `/home/ubuntu/OnTime-back`, writes a production `.env` from GitHub Secrets, verifies private RDS connectivity, and restarts Docker Compose on the EC2 instance.