diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eddf54e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# ----------------------------- +# default +# ----------------------------- +.git +.gitignore +*.log +*.md +.DS_Store +*.iml + +# ----------------------------- +# IDE 관련 +# ----------------------------- +.idea/ +.vscode/ +*.swp + +# ----------------------------- +# OS/툴 관련 캐시 +# ----------------------------- +Thumbs.db +ehthumbs.db +desktop.ini + +# ----------------------------- +# Gradle 빌드 결과물 및 캐시 +# ----------------------------- +build/ +!build/libs/*.jar +.gradle/ +!gradle/wrapper/gradle-wrapper.jar +.gradle/ + +# ----------------------------- +# 환경/인증 관련 (보안 목적) +# ----------------------------- +.env +*.pem +*.crt +*.key + +# ----------------------------- +# 테스트/커버리지 +# ----------------------------- +test-results/ +test-output/ +jacoco*.exec +coverage/ +*.html +*.xml +*.json diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml new file mode 100644 index 0000000..d812f9e --- /dev/null +++ b/.github/workflows/push-cd-dev.yml @@ -0,0 +1,79 @@ +name: Spring CD for Dev Push + +on: + push: + branches: [ dev, main ] + +permissions: + contents: read + +jobs: + build-and-deploy: + name: Build Docker and Deploy to Remote Server + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Build JAR + run: ./gradlew clean bootJar + + - name: Build Docker image + run: | + docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest . + + - name: Log in to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + + - name: Push Docker image + run: docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest + + - name: Setup SSH key and config + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/my-key.pem + chmod 400 ~/.ssh/my-key.pem + ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts + echo -e "Host *\n ServerAliveInterval 60\n ServerAliveCountMax 3" >> ~/.ssh/config + + - name: Create app directory on server + run: | + ssh -i ~/.ssh/my-key.pem ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} "mkdir -p /home/${{ secrets.SERVER_USER }}/app" + + - name: Deploy and Restart Container + run: | + ssh -i ~/.ssh/my-key.pem ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF' + mkdir -p /home/${{ secrets.SERVER_USER }}/app + cd /home/${{ secrets.SERVER_USER }}/app + + echo "${{ secrets.ENV_FILE }}" > /home/${{ secrets.SERVER_USER }}/app/.env + + docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest + docker compose -f docker-compose-dev.yaml down + docker compose -f docker-compose-dev.yaml up -d + + echo "✅ Docker Compose finished. Containers should be up." + EOF + + - name: Check Docker container health + run: | + ssh -i ~/.ssh/my-key.pem ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF' + CONTAINER_ID=$(docker ps -q --filter "ancestor=${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest") + if [ -z "$CONTAINER_ID" ]; then + echo "❌ ERROR: container does not run" + exit 1 + fi + + echo "✅ SUCCESS: container is running" + docker ps --filter "id=$CONTAINER_ID" + EOF diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..256825c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +# Dockerfile +FROM openjdk:21-jdk-slim +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"] diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml new file mode 100644 index 0000000..e8c0b8e --- /dev/null +++ b/docker-compose-dev.yaml @@ -0,0 +1,31 @@ +version: "3.8" + +services: + spring-app: + image: ${DOCKER_USERNAME}/${DOCKER_REPO}:latest + container_name: spring-app + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + env_file: + - .env + volumes: + - ./logs:/app/logs + - ./config:/app/config:ro + restart: always + networks: + - app-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:${SERVER_PORT}/actuator/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + +networks: + app-network: + driver: bridge diff --git a/src/main/resources/application-sample.yml b/src/main/resources/application-sample.yml index b7b97da..c2872d8 100644 --- a/src/main/resources/application-sample.yml +++ b/src/main/resources/application-sample.yml @@ -47,9 +47,9 @@ spring: ignore-accept-header: true jwt: - secret-key: ${JWT_SECRET_KEY:e4f1a5c8d2b7e9f0a6c3d1b8e5f2c7a9d4e6f3b1a2c8d5e9f0b3a7c2d1e8f5a4} - access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600} # seconds (1 hour) - refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400} # seconds (1 day) + secret-key: ${JWT_SECRET_KEY} + access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY} # seconds (1 hour) + refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY} # seconds (1 day) springdoc: swagger-ui: @@ -94,7 +94,7 @@ openapi: default-timeout-seconds: 5 auction-history: delay-ms: 1000 - cron: "0 0 */1 * * *" # 1시간마다 실행 + cron: "0 0 */4 * * *" # 1시간마다 실행 min-price: cron: "0 0 */4 * * *"