Skip to content

Commit 6e5769b

Browse files
authored
Merge pull request #294 from DevKor-github/codexd/merge-main-into-deploy
Merge main into deploy
2 parents 56ac641 + 1068c6c commit 6e5769b

61 files changed

Lines changed: 844 additions & 424 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy.yml

Lines changed: 135 additions & 131 deletions
Large diffs are not rendered by default.

docs/deployment.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Production Deployment
2+
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.
4+
5+
## Required GitHub Secrets
6+
7+
Deployment access:
8+
9+
- `EC2_HOST`
10+
- `EC2_USER`
11+
- `EC2_SSH_KEY`
12+
- `GHCR_USERNAME`
13+
- `GHCR_READ_TOKEN`
14+
15+
Runtime image and port:
16+
17+
- `BACKEND_HTTP_PORT` (optional, defaults to `8080`)
18+
19+
Spring and database:
20+
21+
- `SPRING_APPLICATION_NAME`
22+
- `SPRING_DATASOURCE_URL`
23+
- `SPRING_DATASOURCE_USERNAME`
24+
- `SPRING_DATASOURCE_PASSWORD`
25+
- `SPRING_DATASOURCE_DRIVER_CLASS_NAME`
26+
- `SPRING_JPA_HIBERNATE_DDL_AUTO`
27+
- `SPRING_FLYWAY_URL`
28+
- `SPRING_FLYWAY_USER`
29+
- `SPRING_FLYWAY_PASSWORD`
30+
31+
Authentication and OAuth:
32+
33+
- `JWT_SECRETKEY`
34+
- `JWT_ACCESS_EXPIRATION`
35+
- `JWT_REFRESH_EXPIRATION`
36+
- `JWT_ACCESS_HEADER`
37+
- `JWT_REFRESH_HEADER`
38+
- `GOOGLE_WEB_CLIENT_ID`
39+
- `GOOGLE_APP_CLIENT_ID`
40+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET`
41+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE`
42+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI`
43+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE`
44+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME`
45+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI`
46+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI`
47+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI`
48+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE`
49+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID`
50+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE`
51+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI`
52+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE`
53+
- `SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME`
54+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI`
55+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI`
56+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI`
57+
- `SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE`
58+
- `APPLE_CLIENT_ID`
59+
- `APPLE_TEAM_ID`
60+
- `APPLE_LOGIN_KEY`
61+
- `APPLE_PRIVATE_KEY_BASE64`
62+
- `FEATURE_APPLE_LOGIN_ENABLED` (optional, defaults to `true`)
63+
64+
Firebase:
65+
66+
- `FIREBASE_CREDENTIALS_BASE64`
67+
68+
Set the base64 secrets from the ignored local credential files:
69+
70+
```bash
71+
base64 -i ontime-back/src/main/resources/ontime-c63f1-firebase-adminsdk-fbsvc-a043cdc829.json | tr -d '\n' | gh secret set FIREBASE_CREDENTIALS_BASE64 --repo DevKor-github/OnTime-back
72+
```
73+
74+
```bash
75+
base64 -i ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8 | tr -d '\n' | gh secret set APPLE_PRIVATE_KEY_BASE64 --repo DevKor-github/OnTime-back
76+
```
77+
78+
## Build And Release Flow
79+
80+
Push to the `deploy` branch to trigger `.github/workflows/deploy.yml`.
81+
82+
The workflow:
83+
84+
1. Builds `ontime-back/Dockerfile` from the `ontime-back/` context.
85+
2. Pushes two GHCR tags:
86+
- `ghcr.io/devkor-github/ontime-back:<commit-sha>`
87+
- `ghcr.io/devkor-github/ontime-back:deploy-latest`
88+
3. Uploads `docker-compose.yml` to `/home/ubuntu/OnTime-back`.
89+
4. Writes `/home/ubuntu/OnTime-back/.env` from GitHub secrets.
90+
5. Runs `docker compose pull && docker compose up -d --remove-orphans`.
91+
6. Waits until the `ontime-container` Docker health status is `healthy`.
92+
93+
## Health Verification
94+
95+
The production image exposes a Docker healthcheck against:
96+
97+
```text
98+
/actuator/health/readiness
99+
```
100+
101+
Manual checks on EC2:
102+
103+
```bash
104+
cd /home/ubuntu/OnTime-back
105+
sudo docker compose ps
106+
sudo docker inspect -f '{{.State.Health.Status}}' ontime-container
107+
curl -fsS http://localhost:8080/actuator/health/readiness
108+
```
109+
110+
## Rollback
111+
112+
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:
113+
114+
```bash
115+
cd /home/ubuntu/OnTime-back
116+
sudo docker compose pull
117+
sudo docker compose up -d --remove-orphans
118+
sudo docker inspect -f '{{.State.Health.Status}}' ontime-container
119+
```
120+
121+
Keep the previous SHA in the release notes or GitHub Actions deploy history so rollback does not depend on `deploy-latest`.

ontime-back/.dockerignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.env
2+
.git
3+
.gitignore
4+
.gradle
5+
build
6+
out
7+
bin
8+
.idea
9+
.vscode
10+
*.iml
11+
*.iws
12+
*.ipr
13+
.DS_Store
14+
15+
src/main/resources/application.properties
16+
src/main/resources/*.json
17+
src/main/resources/key
18+
src/main/resources/**/*.p8
19+
20+
Dockerfile
21+
docker-compose*.yml

ontime-back/Dockerfile

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,40 @@
1-
FROM eclipse-temurin:17-jre
2-
RUN apt-get update && \
3-
apt-get install -y tzdata && \
4-
ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
5-
echo "Asia/Seoul" > /etc/timezone && \
6-
apt-get clean && \
7-
rm -rf /var/lib/apt/lists/*
1+
FROM eclipse-temurin:17-jdk-jammy AS builder
2+
3+
WORKDIR /workspace
4+
5+
COPY gradlew build.gradle settings.gradle ./
6+
COPY gradle ./gradle
7+
RUN chmod +x ./gradlew
8+
9+
COPY src ./src
10+
RUN ./gradlew clean bootJar --no-daemon
11+
12+
FROM eclipse-temurin:17-jre-jammy
13+
14+
ENV TZ=Asia/Seoul \
15+
LANG=C.UTF-8 \
16+
SPRING_PROFILES_ACTIVE=prod \
17+
JAVA_TOOL_OPTIONS="-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom"
18+
19+
RUN apt-get update \
20+
&& apt-get install -y --no-install-recommends curl tzdata \
21+
&& ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \
22+
&& echo "${TZ}" > /etc/timezone \
23+
&& groupadd --system app \
24+
&& useradd --system --gid app --home-dir /app --shell /usr/sbin/nologin app \
25+
&& mkdir -p /app \
26+
&& chown -R app:app /app \
27+
&& apt-get clean \
28+
&& rm -rf /var/lib/apt/lists/*
29+
830
WORKDIR /app
9-
COPY project.jar app.jar
31+
COPY --from=builder --chown=app:app /workspace/build/libs/*.jar /app/app.jar
32+
33+
USER app
34+
EXPOSE 8080
35+
STOPSIGNAL SIGTERM
36+
37+
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
38+
CMD curl -fsS "http://localhost:${SERVER_PORT:-8080}/actuator/health/readiness" || exit 1
39+
1040
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

ontime-back/docker-compose.yml

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
services:
22
backend:
3-
build:
4-
context: .
5-
dockerfile: Dockerfile
6-
image: ontime-backend
7-
container_name: ontime-backend
8-
restart: unless-stopped
3+
image: "${BACKEND_IMAGE:-ghcr.io/devkor-github/ontime-back}:${IMAGE_TAG:?IMAGE_TAG is required}"
4+
container_name: "${BACKEND_CONTAINER_NAME:-ontime-container}"
5+
env_file:
6+
- .env
7+
environment:
8+
SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE:-prod}"
9+
SERVER_PORT: "${SERVER_PORT:-8080}"
10+
JAVA_TOOL_OPTIONS: "${JAVA_TOOL_OPTIONS:--XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom}"
911
ports:
10-
- "8080:8080"
11-
volumes:
12-
- ./config/application.properties:/app/config/application.properties:ro
13-
- ./secrets/firebase-adminsdk.json:/app/secrets/firebase-adminsdk.json:ro
14-
- ./secrets/AuthKey_743M7R5W3W.p8:/app/secrets/AuthKey_743M7R5W3W.p8:ro
12+
- "${BACKEND_HTTP_PORT:-8080}:${SERVER_PORT:-8080}"
13+
restart: unless-stopped
14+
stop_grace_period: 30s
15+
mem_limit: "${BACKEND_MEMORY_LIMIT:-768m}"
16+
cpus: "${BACKEND_CPU_LIMIT:-1.0}"
17+
healthcheck:
18+
test: ["CMD-SHELL", "curl -fsS http://localhost:${SERVER_PORT:-8080}/actuator/health/readiness || exit 1"]
19+
interval: 30s
20+
timeout: 5s
21+
start_period: 60s
22+
retries: 3
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Logging Redaction Policy
2+
3+
Production logs must only contain operational metadata needed to debug routing,
4+
ownership, status, and latency. Request payloads are not safe log data.
5+
6+
## Request Logs
7+
8+
Controller request logging is centralized in `LoggingAspect` and
9+
`RequestLogPolicy`. Request logs must include only:
10+
11+
- request ID
12+
- route
13+
- method
14+
- actor identifier
15+
- client IP
16+
- response status
17+
- timing in milliseconds
18+
19+
The request logger must not inspect or render `@RequestBody` arguments.
20+
21+
## Sensitive Fields
22+
23+
Never log values or raw key/value payloads for credentials, OAuth material,
24+
profile text, notes, or request bodies. Sensitive key names include:
25+
26+
- `authorization`
27+
- `firebaseToken`
28+
- `password`
29+
- `secret`
30+
- `token`
31+
32+
When field-level logging is genuinely needed, add the field to
33+
`RequestLogPolicy`'s allowlist and keep the log statement metadata-only.
34+
35+
## Regression Guard
36+
37+
`SensitiveLoggingPolicyTest` scans application source for sensitive key names in
38+
logger calls, request-body logging in `LoggingAspect`, and DTO-generated
39+
`toString` methods. Any future exception must update this policy and the
40+
central allowlist in the same change.

0 commit comments

Comments
 (0)