Skip to content
Closed
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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.github
.gradle
.idea
build
out
*.iml
*.iws
*.ipr
.DS_Store
*.log
.env
.env.*
application-local.yml
30 changes: 30 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
DB_URL=jdbc:postgresql://localhost:5432/jobdri
DB_USERNAME=jobdri
DB_PASSWORD=change-me
DB_DRIVER=org.postgresql.Driver
JPA_DDL_AUTO=update

REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_SSL_ENABLED=false

JWT_SECRET_KEY=base64-encoded-secret
JWT_ACCESS_TOKEN_EXPIRATION=3600000
JWT_REFRESH_TOKEN_EXPIRATION=1209600000

MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=jobdri.official@gmail.com
MAIL_PASSWORD=change-me
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS_ENABLE=true
MAIL_SMTP_CONNECTION_TIMEOUT=5000
MAIL_SMTP_TIMEOUT=5000
MAIL_SMTP_WRITE_TIMEOUT=5000

GOOGLE_CLIENT_ID=change-me.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=change-me
APP_OAUTH2_REDIRECT_URI=http://localhost:3000/oauth2/redirect

MANAGEMENT_HEALTH_SHOW_DETAILS=always
34 changes: 34 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
APP_PORT=8080
IMAGE_NAME=ghcr.io/jobdri-developer/backend
IMAGE_TAG=latest

DB_URL=jdbc:postgresql://your-db-host:5432/jobdri
DB_USERNAME=jobdri
DB_PASSWORD=change-me
DB_DRIVER=org.postgresql.Driver
JPA_DDL_AUTO=update

REDIS_HOST=your-upstash-host
REDIS_PORT=6379
REDIS_PASSWORD=change-me
REDIS_SSL_ENABLED=true

JWT_SECRET_KEY=base64-encoded-secret
JWT_ACCESS_TOKEN_EXPIRATION=3600000
JWT_REFRESH_TOKEN_EXPIRATION=1209600000

MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=jobdri.official@gmail.com
MAIL_PASSWORD=change-me
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS_ENABLE=true
MAIL_SMTP_CONNECTION_TIMEOUT=5000
MAIL_SMTP_TIMEOUT=5000
MAIL_SMTP_WRITE_TIMEOUT=5000

GOOGLE_CLIENT_ID=change-me.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=change-me
APP_OAUTH2_REDIRECT_URI=https://your-frontend-domain/oauth2/redirect

MANAGEMENT_HEALTH_SHOW_DETAILS=never
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
pull_request:
branches:
- main
- develop
push:
branches:
- main
- develop

permissions:
contents: read

jobs:
test:
name: Test
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: gradle

- name: Grant Gradle permission
run: chmod +x ./gradlew

- name: Run tests
run: ./gradlew --no-daemon clean test

docker-build:
name: Docker Build
runs-on: ubuntu-latest
needs: test

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build Docker image
run: docker build -t jobdri-api:${{ github.sha }} .
77 changes: 77 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Deploy

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read
packages: write

env:
IMAGE_NAME: ghcr.io/jobdri-developer/backend

jobs:
build-and-push:
name: Build and Push Image
runs-on: ubuntu-latest

steps:
- name: Checkout
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: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.IMAGE_NAME }}:latest
${{ env.IMAGE_NAME }}:${{ github.sha }}

deploy:
name: Deploy to Server
runs-on: ubuntu-latest
needs: build-and-push
env:
HAS_DEPLOY_SECRETS: ${{ secrets.DEPLOY_HOST != '' && secrets.DEPLOY_USER != '' && secrets.DEPLOY_SSH_KEY != '' && secrets.DEPLOY_PATH != '' && secrets.GHCR_USERNAME != '' && secrets.GHCR_TOKEN != '' }}

steps:
- name: Skip deploy when server secrets are missing
if: env.HAS_DEPLOY_SECRETS != 'true'
run: echo "Deployment skipped because required server secrets are not configured."

- name: Deploy with Docker Compose
if: env.HAS_DEPLOY_SECRETS == 'true'
uses: appleboy/ssh-action@v1.2.0
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}
IMAGE_TAG: latest
GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }}
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: ${{ secrets.DEPLOY_PORT || 22 }}
envs: IMAGE_NAME,IMAGE_TAG,GHCR_USERNAME,GHCR_TOKEN
script: |
cd "${{ secrets.DEPLOY_PATH }}"
echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin
export IMAGE_NAME="$IMAGE_NAME"
export IMAGE_TAG="$IMAGE_TAG"
docker compose -f docker-compose.prod.yml pull api
docker compose -f docker-compose.prod.yml up -d api
docker image prune -f
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
HELP.md
.gradle
.gradle-home
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
Expand Down Expand Up @@ -44,6 +45,9 @@ out/

# Env
.env
.env.*
!.env.example
!.env.production.example

# application secrets
application-local.yml
application-local.yml
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /workspace

COPY gradlew settings.gradle build.gradle ./
COPY gradle ./gradle
RUN chmod +x gradlew

COPY src ./src
RUN ./gradlew --no-daemon clean bootJar -x test

FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

RUN addgroup -S jobdri && adduser -S jobdri -G jobdri

COPY --from=builder /workspace/build/libs/*.jar app.jar

USER jobdri

EXPOSE 8080

ENV SPRING_PROFILES_ACTIVE=prod
ENV JAVA_OPTS=""

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,35 @@
# BackEnd
Repository of JobDri BackEnd

## Docker

로컬 실행:

```bash
cp .env.example .env
docker compose up --build
```

배포 서버 실행:

```bash
cp .env.production.example .env
docker compose -f docker-compose.prod.yml up -d
```

`prod` 프로필은 `/actuator/health`를 노출합니다.

## CI/CD

- `CI`: `main`, `develop` 브랜치 push 및 PR에서 테스트와 Docker 이미지 빌드를 실행합니다.
- `Deploy`: `main` 브랜치 push 또는 수동 실행 시 GHCR에 이미지를 푸시하고, 배포 서버 secret이 있으면 SSH로 `docker-compose.prod.yml`을 갱신합니다.

GitHub Actions 배포 secret:

- `DEPLOY_HOST`
- `DEPLOY_USER`
- `DEPLOY_PORT` optional, default `22`
- `DEPLOY_SSH_KEY`
- `DEPLOY_PATH`
- `GHCR_USERNAME`
- `GHCR_TOKEN`
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'

//web
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

Expand Down
11 changes: 11 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
api:
image: ${IMAGE_NAME:-ghcr.io/jobdri-developer/backend}:${IMAGE_TAG:-latest}
container_name: jobdri-api
env_file:
- .env
environment:
SPRING_PROFILES_ACTIVE: prod
ports:
- "${APP_PORT:-8080}:8080"
restart: unless-stopped
59 changes: 59 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
services:
api:
build:
context: .
dockerfile: Dockerfile
container_name: jobdri-api
env_file:
- .env
environment:
SPRING_PROFILES_ACTIVE: prod
DB_URL: ${DB_URL:-jdbc:postgresql://postgres:5432/jobdri}
DB_USERNAME: ${DB_USERNAME:-jobdri}
DB_PASSWORD: ${DB_PASSWORD:-jobdri}
DB_DRIVER: ${DB_DRIVER:-org.postgresql.Driver}
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD:
REDIS_SSL_ENABLED: false
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped

redis:
image: redis:7-alpine
container_name: jobdri-redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

postgres:
image: postgres:16-alpine
container_name: jobdri-postgres
environment:
POSTGRES_DB: ${POSTGRES_DB:-jobdri}
POSTGRES_USER: ${DB_USERNAME:-jobdri}
POSTGRES_PASSWORD: ${DB_PASSWORD:-jobdri}
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-jobdri} -d ${POSTGRES_DB:-jobdri}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

volumes:
postgres-data:
Loading
Loading