Skip to content

Commit cc20499

Browse files
committed
feat: DockerHub 이미지 업로드 기능 추가
1 parent b7106e2 commit cc20499

File tree

7 files changed

+640
-400
lines changed

7 files changed

+640
-400
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
cache-from: type=gha
7777
cache-to: type=gha,mode=max
7878
context: .
79-
file: ./infra/Dockerfile
79+
file: ./infra/lambda.Dockerfile
8080
platforms: linux/amd64
8181
provenance: false
8282
build-args: |
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
name: Release
2+
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
5+
cancel-in-progress: true
6+
7+
on:
8+
workflow_dispatch:
9+
inputs:
10+
WORKFLOW_PHASE:
11+
description: "배포할 대상 환경을 선택하세요."
12+
required: true
13+
default: dev
14+
type: choice
15+
options:
16+
- dev
17+
- prod
18+
push:
19+
branches:
20+
- "main"
21+
22+
jobs:
23+
BuildAndDeploy:
24+
runs-on: ubuntu-latest
25+
26+
env:
27+
API_STAGE: ${{ github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev' }}
28+
BUMP_RULE: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && '--stage' || '' }}
29+
30+
steps:
31+
# Checkout source codes
32+
- name: Checkout source codes
33+
uses: actions/checkout@v4
34+
with:
35+
fetch-depth: 0
36+
37+
- name: Install uv
38+
uses: astral-sh/setup-uv@v5
39+
with:
40+
enable-cache: true
41+
ignore-nothing-to-cache: true
42+
43+
- name: Install dependencies
44+
run: uv sync --only-group=deployment
45+
46+
- name: Set up Docker Buildx
47+
uses: docker/setup-buildx-action@v3
48+
49+
- name: Login to Docker Hub
50+
uses: docker/login-action@v3
51+
with:
52+
username: ${{ secrets.DOCKERHUB_USERNAME }}
53+
password: ${{ secrets.DOCKERHUB_TOKEN }}
54+
55+
- name: Get current date, repo name and release tag version
56+
id: info
57+
run: |
58+
LATEST_TAG=$(git tag -l --sort=-creatordate | head -n 1)
59+
echo "::set-output name=TAG::$(python ./.github/scripts/get_new_version.py --current=$LATEST_TAG ${{ env.BUMP_RULE }})"
60+
echo "::set-output name=date::$(date +'%Y-%m-%d_%H:%M:%S')"
61+
echo "::set-output name=repository_name::$(echo ${{ github.repository }} | sed -e 's/${{ github.repository_owner }}\///')"
62+
63+
# Build and Push Docker image to Docker Hub
64+
- name: Build and Push Docker image to Docker Hub
65+
uses: docker/build-push-action@v5
66+
with:
67+
push: true
68+
tags: |
69+
${{ secrets.DOCKERHUB_IMAGE }}:${{ steps.info.outputs.TAG }}
70+
${{ secrets.DOCKERHUB_IMAGE }}:latest
71+
cache-from: type=gha
72+
cache-to: type=gha,mode=max
73+
context: .
74+
file: ./infra/server.Dockerfile
75+
platforms: linux/amd64
76+
provenance: false
77+
build-args: |
78+
RELEASE_VERSION=${{ steps.info.outputs.TAG }}
79+
GIT_HASH=${{ github.sha }}
80+
IMAGE_BUILD_DATETIME=${{ steps.info.outputs.date }}
81+
82+
# Create git tag
83+
- name: Create and push git tag
84+
run: |
85+
git tag ${{ steps.info.outputs.TAG }}
86+
git push origin ${{ steps.info.outputs.TAG }}
87+
88+
# Notify to Slack (Success)
89+
- name: Notify deployment to Slack
90+
if: failure() || cancelled()
91+
uses: slackapi/slack-github-action@v1.26.0
92+
with:
93+
channel-id: ${{ vars.SLACK_DEPLOYMENT_ALERT_CHANNEL }}
94+
payload: |
95+
{
96+
"blocks": [
97+
{
98+
"type": "header",
99+
"text": {
100+
"type": "plain_text",
101+
"text": "${{ steps.info.outputs.repository_name }} ${{ steps.info.outputs.TAG }} 버전 Build & Push 실패 :rotating_light: (${{ job.status }})",
102+
"emoji": true
103+
}
104+
},
105+
{
106+
"type": "section",
107+
"text": {"type": "mrkdwn", "text": "GitHub Action 바로가기"},
108+
"accessory": {
109+
"type": "button",
110+
"text": {"type": "plain_text", "text": "${{ github.run_id }}"},
111+
"value": "github_action",
112+
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
113+
"action_id": "button-action"
114+
}
115+
}
116+
]
117+
}
118+
env:
119+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
120+
121+
# Notify to Slack (Failure)
122+
- name: Notify deployment to Slack
123+
uses: slackapi/slack-github-action@v1.26.0
124+
with:
125+
channel-id: ${{ vars.SLACK_DEPLOYMENT_ALERT_CHANNEL }}
126+
payload: |
127+
{
128+
"blocks": [
129+
{
130+
"type": "header",
131+
"text": {
132+
"type": "plain_text",
133+
"text": "${{ steps.info.outputs.repository_name }} ${{ steps.info.outputs.TAG }} 버전 Build & Push 성공 :tada:",
134+
"emoji": true
135+
}
136+
},
137+
{
138+
"type": "section",
139+
"text": {"type": "mrkdwn", "text": "GitHub Action 바로가기"},
140+
"accessory": {
141+
"type": "button",
142+
"text": {"type": "plain_text", "text": "${{ github.run_id }}"},
143+
"value": "github_action",
144+
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
145+
"action_id": "button-action"
146+
}
147+
}
148+
]
149+
}
150+
env:
151+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Makefile

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@ PROJECT_DIR := $(dir $(MKFILE_PATH))
33

44
# Set additional build args for docker image build using make arguments
55
IMAGE_NAME := pycon_backend
6-
ifeq (docker-build,$(firstword $(MAKECMDGOALS)))
7-
TAG_NAME := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
8-
$(eval $(TAG_NAME):;@:)
9-
endif
10-
TAG_NAME := $(if $(TAG_NAME),$(TAG_NAME),local)
11-
CONTAINER_NAME = $(IMAGE_NAME)_$(TAG_NAME)_container
6+
LAMBDA_CONTAINER_NAME = $(IMAGE_NAME)_lambda_container
7+
SERVER_CONTAINER_NAME = $(IMAGE_NAME)_server_container
128

139
ifeq ($(DOCKER_DEBUG),true)
1410
DOCKER_MID_BUILD_OPTIONS = --progress=plain --no-cache
@@ -107,39 +103,68 @@ zappa-export:
107103
uv run zappa save-python-settings-file
108104

109105
# =============================================================================
110-
# Docker related commands
106+
# Docker related commands (Lambda)
111107

112-
# Docker image build
113-
# Usage: make docker-build <tag-name:=local>
114-
# if you want to build with debug mode, set DOCKER_DEBUG=true
115-
# ex) make docker-build or make docker-build some_TAG_NAME DOCKER_DEBUG=true
116-
docker-build:
108+
# Lambda Docker image build
109+
docker-lambda-build:
117110
@docker build \
118-
-f ./infra/Dockerfile -t $(IMAGE_NAME):$(TAG_NAME) \
111+
-f ./infra/lambda.Dockerfile -t $(IMAGE_NAME):lambda \
119112
--build-arg GIT_HASH=$(shell git rev-parse HEAD) \
120113
--build-arg IMAGE_BUILD_DATETIME=$(shell date +%Y-%m-%d_%H:%M:%S) \
121114
$(DOCKER_MID_BUILD_OPTIONS) $(PROJECT_DIR) $(DOCKER_END_BUILD_OPTIONS)
122115

123-
docker-run: docker-compose-up
124-
@(docker stop $(CONTAINER_NAME) || true && docker rm $(CONTAINER_NAME) || true) > /dev/null 2>&1
116+
docker-lambda-run: docker-compose-up
117+
@(docker stop $(LAMBDA_CONTAINER_NAME) || true && docker rm $(LAMBDA_CONTAINER_NAME) || true) > /dev/null 2>&1
125118
@docker run -d --rm \
126119
-p 48000:8080 \
127120
--env-file envfile/.env.local --env-file envfile/.env.docker \
128-
--name $(CONTAINER_NAME) \
129-
$(IMAGE_NAME):$(TAG_NAME)
121+
--name $(LAMBDA_CONTAINER_NAME) \
122+
$(IMAGE_NAME):lambda
130123

131-
docker-readyz:
124+
docker-lambda-readyz:
132125
curl -X POST http://localhost:48000/2015-03-31/functions/function/invocations -d $(AWS_LAMBDA_READYZ_PAYLOAD) | jq '.body | fromjson'
133126

134-
docker-test: docker-run docker-readyz
127+
docker-lambda-test: docker-lambda-run docker-lambda-readyz
128+
129+
docker-lambda-build-and-test: docker-lambda-build docker-lambda-test
130+
131+
docker-lambda-stop:
132+
docker stop $(LAMBDA_CONTAINER_NAME) || true
133+
134+
docker-lambda-rm: docker-lambda-stop
135+
docker rm $(LAMBDA_CONTAINER_NAME) || true
136+
137+
# =============================================================================
138+
# Docker related commands (Server)
139+
140+
# Server Docker image build
141+
docker-server-build:
142+
@docker build \
143+
-f ./infra/server.Dockerfile -t $(IMAGE_NAME):server \
144+
--build-arg GIT_HASH=$(shell git rev-parse HEAD) \
145+
--build-arg IMAGE_BUILD_DATETIME=$(shell date +%Y-%m-%d_%H:%M:%S) \
146+
$(DOCKER_MID_BUILD_OPTIONS) $(PROJECT_DIR) $(DOCKER_END_BUILD_OPTIONS)
147+
148+
docker-server-run: docker-compose-up
149+
@(docker stop $(SERVER_CONTAINER_NAME) || true && docker rm $(SERVER_CONTAINER_NAME) || true) > /dev/null 2>&1
150+
@docker run -d --rm \
151+
-p 8000:8000 \
152+
--env-file envfile/.env.local --env-file envfile/.env.docker \
153+
--name $(SERVER_CONTAINER_NAME) \
154+
$(IMAGE_NAME):server
155+
156+
docker-server-readyz:
157+
curl -s http://localhost:8000/readyz/ | jq '.'
158+
159+
docker-server-test: docker-server-run docker-server-readyz
135160

136-
docker-build-and-test: docker-build docker-test
161+
docker-server-build-and-test: docker-server-build docker-server-test
137162

138-
docker-stop:
139-
docker stop $(CONTAINER_NAME) || true
163+
docker-server-stop:
164+
docker stop $(SERVER_CONTAINER_NAME) || true
140165

141-
docker-rm: docker-stop
142-
docker rm $(CONTAINER_NAME) || true
166+
docker-server-rm: docker-server-stop
167+
docker rm $(SERVER_CONTAINER_NAME) || true
143168

144169
# Docker compose setup
145170
# Below commands are for local development only

infra/server.Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
ARG PYTHON_VERSION=3.12
2+
FROM python:${PYTHON_VERSION}-slim
3+
WORKDIR /app
4+
SHELL [ "/bin/bash", "-euxvc"]
5+
6+
ENV PATH="${PATH}:/root/.local/bin:" \
7+
TZ=Asia/Seoul \
8+
LANG=C.UTF-8 \
9+
LC_ALL=C.UTF-8 \
10+
PYTHONIOENCODING=UTF-8 \
11+
PYTHONUNBUFFERED=1 \
12+
UV_COMPILE_BYTECODE=1 \
13+
UV_CONCURRENT_DOWNLOADS=32 \
14+
UV_LINK_MODE=copy \
15+
UV_PROJECT_ENVIRONMENT="/usr/local" \
16+
UV_PYTHON_DOWNLOADS=0
17+
18+
# Setup timezone and install system dependencies
19+
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
20+
21+
# Install dependencies
22+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
23+
COPY --chown=nobody:nobody pyproject.toml uv.lock ./
24+
RUN --mount=type=cache,target=/root/.cache/uv \
25+
apt-get update \
26+
&& apt-get install -y --no-install-recommends gcc libpq-dev \
27+
&& rm -rf /var/lib/apt/lists/* \
28+
&& uv sync --no-default-groups --frozen
29+
30+
ARG GIT_HASH
31+
ARG RELEASE_VERSION=unknown
32+
ENV DEPLOYMENT_GIT_HASH=$GIT_HASH
33+
ENV DEPLOYMENT_RELEASE_VERSION=$RELEASE_VERSION
34+
35+
# Make docker to always copy app directory so that source code can be refreshed.
36+
ARG IMAGE_BUILD_DATETIME=unknown
37+
ENV DEPLOYMENT_IMAGE_BUILD_DATETIME=$IMAGE_BUILD_DATETIME
38+
39+
# Copy main app
40+
COPY --chown=nobody:nobody app/ ./
41+
42+
ENV DJANGO_SETTINGS_MODULE="core.settings"
43+
44+
EXPOSE 8000
45+
46+
# The reason for using nobody user is to avoid running the app as root, which can be a security risk.
47+
USER nobody
48+
CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "30"]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies = [
2222
"drf-spectacular>=0.28.0",
2323
"drf-standardized-errors[openapi]>=0.14.1",
2424
"faker>=37.3.0",
25+
"gunicorn>=23.0.0",
2526
"google-api-python-client>=2.179.0",
2627
"google-auth-httplib2>=0.2.0",
2728
"google-auth-oauthlib>=1.2.2",

0 commit comments

Comments
 (0)