Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,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
Expand Down Expand Up @@ -154,14 +156,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
Expand All @@ -175,10 +196,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/tcp/$DB_HOST/$DB_PORT"
fi

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
Expand Down
26 changes: 17 additions & 9 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -97,8 +103,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

Expand All @@ -115,6 +122,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
Expand Down
5 changes: 5 additions & 0 deletions ontime-back/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 13 additions & 14 deletions ontime-back/docs/deployment/ec2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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`
Expand All @@ -34,27 +32,28 @@ 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.

## Runtime Files on EC2

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.
Loading