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
104 changes: 104 additions & 0 deletions .github/workflows/cd-prod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: CD PROD

on:
push:
branches:
- prod
workflow_dispatch:

jobs:
deploy-prod:
runs-on: ubuntu-latest

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

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: dykkim/muneo
tags: |
type=raw,value=prod-latest
type=sha,format=short,prefix=prod-

- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Copy prod deploy files to EC2
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
source: "docker-compose.prod.yml,nginx/conf.d/prod.conf"
target: ${{ secrets.EC2_DEPLOY_PATH_PROD }}
strip_components: 0
overwrite: true

- name: Deploy prod stack on EC2
uses: appleboy/ssh-action@v1.0.3
env:
DEPLOY_PATH: ${{ secrets.EC2_DEPLOY_PATH }}
DEPLOY_PATH_PROD: ${{ secrets.EC2_DEPLOY_PATH_PROD }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
DOCKERHUB_IMAGE: dykkim/muneo
PROD_APP_ENV: ${{ secrets.PROD_APP_ENV }}
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
envs: DEPLOY_PATH,DEPLOY_PATH_PROD,DOCKERHUB_USERNAME,DOCKERHUB_TOKEN,DOCKERHUB_IMAGE,PROD_APP_ENV
script_stop: true
script: |
set -e

sudo systemctl enable --now docker || true

mkdir -p "${DEPLOY_PATH_PROD}"
cd "${DEPLOY_PATH_PROD}"
mkdir -p nginx/conf.d

printf "%s" "${PROD_APP_ENV}" > .env
{
echo ""
echo "DOCKERHUB_IMAGE=${DOCKERHUB_IMAGE}"
echo "IMAGE_TAG=prod-latest"
} >> .env

echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin

docker compose -f docker-compose.prod.yml pull app-prod redis-prod
docker compose -f docker-compose.prod.yml up -d --remove-orphans app-prod redis-prod

# Keep one shared nginx stack (dev path), and update only its prod server block.
mkdir -p "${DEPLOY_PATH}/nginx/conf.d"
cp -f "${DEPLOY_PATH_PROD}/nginx/conf.d/prod.conf" "${DEPLOY_PATH}/nginx/conf.d/prod.conf"

cd "${DEPLOY_PATH}"
docker compose exec -T nginx nginx -t
docker compose exec -T nginx nginx -s reload
docker compose exec -T nginx sh -c "grep -n 'api-prod.muneo.ai.kr\\|risk-detector\\|proxy_read_timeout\\|proxy_request_buffering' /etc/nginx/conf.d/prod.conf || true"

docker image prune -f
2 changes: 1 addition & 1 deletion .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
source: "docker-compose.yml,nginx/conf.d/default.conf"
source: "docker-compose.yml,nginx/conf.d/default.conf,nginx/conf.d/prod.conf"
target: ${{ secrets.EC2_DEPLOY_PATH }}
strip_components: 0
overwrite: true
Expand Down
69 changes: 69 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
services:
app-prod:
image: ${DOCKERHUB_IMAGE:-dykkim/muneo}:${IMAGE_TAG:-latest}
container_name: muneo-app-prod
restart: unless-stopped
expose:
- "8080"
environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
SERVER_PORT: ${SERVER_PORT:-8080}
SERVER_FORWARD_HEADERS_STRATEGY: framework
REDIS_HOST: redis-prod
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis1234}
JWT_SECRET: ${JWT_SECRET}
JWT_ACCESS_TOKEN_EXPIRATION: ${JWT_ACCESS_TOKEN_EXPIRATION:-3600}
JWT_REFRESH_TOKEN_EXPIRATION: ${JWT_REFRESH_TOKEN_EXPIRATION:-1209600}
AI_API_BASE_URL: ${AI_API_BASE_URL}
AI_API_CONNECT_TIMEOUT_MS: ${AI_API_CONNECT_TIMEOUT_MS:-5000}
AI_API_READ_TIMEOUT_MS: ${AI_API_READ_TIMEOUT_MS:-120000}
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
OAUTH_FRONTEND_SUCCESS_URI: ${OAUTH_FRONTEND_SUCCESS_URI}
OAUTH_FRONTEND_SIGNUP_URI: ${OAUTH_FRONTEND_SIGNUP_URI}
OAUTH_FRONTEND_FAILURE_URI: ${OAUTH_FRONTEND_FAILURE_URI}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS}
COOKIE_SAME_SITE: ${COOKIE_SAME_SITE:-Lax}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
COOKIE_SECURE: ${COOKIE_SECURE}
EMAIL_FEATURE_ENABLED: ${EMAIL_FEATURE_ENABLED:-false}
PROD_DB_URL: ${PROD_DB_URL}
PROD_DB_USERNAME: ${PROD_DB_USERNAME}
PROD_DB_PASSWORD: ${PROD_DB_PASSWORD}
DDL_AUTO: ${DDL_AUTO:-validate}
depends_on:
redis-prod:
condition: service_healthy
networks:
- muneo-shared-net
- muneo-prod-net

redis-prod:
image: redis:7-alpine
container_name: muneo-redis-prod
restart: unless-stopped
command: >
sh -c "redis-server --appendonly yes
--requirepass '${REDIS_PASSWORD:-redis1234}'"
volumes:
- redis-prod-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a '${REDIS_PASSWORD:-redis1234}' ping | grep PONG"]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s
networks:
- muneo-prod-net

volumes:
redis-prod-data:

networks:
muneo-shared-net:
external: true
name: muneo-shared-net
muneo-prod-net:
name: muneo-prod-net
driver: bridge
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ services:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certs:/etc/nginx/certs:ro
networks:
- muneo-net
Expand All @@ -77,4 +77,5 @@ volumes:

networks:
muneo-net:
name: muneo-shared-net
driver: bridge
55 changes: 55 additions & 0 deletions nginx/conf.d/prod.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
server {
listen 80;
server_name api-prod.muneo.ai.kr;

return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
http2 on;

server_name api-prod.muneo.ai.kr;

ssl_certificate /etc/nginx/certs/muneo.ai.kr.pem;
ssl_certificate_key /etc/nginx/certs/muneo.ai.kr.key;

client_max_body_size 20m;

# Long-running AI analysis endpoints need higher upstream timeouts than nginx defaults (60s).
location = /api/v1/risk-detector/analyze {
proxy_pass http://app-prod:8080;

proxy_http_version 1.1;
proxy_request_buffering off;
proxy_connect_timeout 10s;
proxy_send_timeout 180s;
proxy_read_timeout 180s;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;
}

location / {
proxy_pass http://app-prod:8080;

proxy_http_version 1.1;
proxy_connect_timeout 10s;
proxy_send_timeout 180s;
proxy_read_timeout 180s;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
Empty file.
Loading