Skip to content

Commit 485ea05

Browse files
committed
feat: workflow 추가
1 parent a2ee4fd commit 485ea05

7 files changed

Lines changed: 305 additions & 0 deletions

File tree

.github/workflows/_build-image.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Build Image
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
component:
7+
description: 'Component name (e.g. frontend, backend) — used for cache scope and image name'
8+
required: true
9+
type: string
10+
dockerfile:
11+
description: 'Path to the Dockerfile'
12+
required: true
13+
type: string
14+
image:
15+
description: 'Image name (without registry)'
16+
required: true
17+
type: string
18+
version:
19+
description: 'Version tag to apply (e.g. run number)'
20+
required: true
21+
type: string
22+
no_cache:
23+
description: 'If true, ignore build cache (use when build secrets changed)'
24+
required: false
25+
type: boolean
26+
default: false
27+
secrets:
28+
BUILD_ENV:
29+
description: 'Optional build-time env file contents, mounted as secret id=build_env'
30+
required: false
31+
32+
jobs:
33+
build:
34+
runs-on: ubuntu-latest
35+
concurrency:
36+
group: build-${{ inputs.component }}
37+
cancel-in-progress: true
38+
permissions:
39+
contents: read
40+
packages: write
41+
steps:
42+
- uses: actions/checkout@v6
43+
44+
- uses: docker/setup-buildx-action@v4
45+
46+
- name: Log in to GHCR
47+
uses: docker/login-action@v4
48+
with:
49+
registry: ghcr.io
50+
username: ${{ github.actor }}
51+
password: ${{ secrets.GITHUB_TOKEN }}
52+
53+
- name: Extract metadata
54+
id: meta
55+
uses: docker/metadata-action@v6
56+
with:
57+
images: ghcr.io/${{ inputs.image }}
58+
tags: |
59+
type=raw,value=${{ inputs.version }}
60+
type=raw,value=latest,enable={{is_default_branch}}
61+
62+
- name: Build and push
63+
uses: docker/build-push-action@v7
64+
with:
65+
context: .
66+
file: ${{ inputs.dockerfile }}
67+
push: true
68+
tags: ${{ steps.meta.outputs.tags }}
69+
labels: ${{ steps.meta.outputs.labels }}
70+
no-cache: ${{ inputs.no_cache }}
71+
cache-from: ${{ inputs.no_cache && '' || format('type=gha,scope={0}', inputs.component) }}
72+
cache-to: type=gha,mode=max,scope=${{ inputs.component }}
73+
secrets: |
74+
${{ secrets.BUILD_ENV != '' && format('build_env={0}', secrets.BUILD_ENV) || '' }}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Deploy Compose
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
frontend_tag:
7+
required: true
8+
type: string
9+
backend_tag:
10+
required: true
11+
type: string
12+
secrets:
13+
BACKEND_ENV:
14+
required: true
15+
16+
jobs:
17+
deploy:
18+
runs-on: greedylabs
19+
concurrency:
20+
group: deploy-greedyreader
21+
cancel-in-progress: false
22+
env:
23+
FRONTEND_TAG: ${{ inputs.frontend_tag }}
24+
BACKEND_TAG: ${{ inputs.backend_tag }}
25+
steps:
26+
- uses: actions/checkout@v6
27+
28+
- name: Log in to GHCR
29+
uses: docker/login-action@v4
30+
with:
31+
registry: ghcr.io
32+
username: ${{ github.actor }}
33+
password: ${{ secrets.GITHUB_TOKEN }}
34+
35+
- name: Write backend env file
36+
run: |
37+
printf '%s' "${{ secrets.BACKEND_ENV }}" > backend.env
38+
chmod 600 backend.env
39+
40+
- name: Pull images
41+
run: docker compose pull
42+
43+
- name: Deploy
44+
run: docker compose up -d --remove-orphans
45+
46+
- name: Prune old images
47+
run: docker image prune -f
48+
49+
- name: Clean up env file
50+
if: always()
51+
run: rm -f backend.env

.github/workflows/deploy.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: Build and Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
inputs:
8+
no_cache:
9+
description: '빌드 캐시 무시 (secret 변경 시 체크)'
10+
type: boolean
11+
default: false
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
jobs:
18+
resolve:
19+
runs-on: ubuntu-latest
20+
outputs:
21+
frontend: ${{ steps.out.outputs.frontend }}
22+
backend: ${{ steps.out.outputs.backend }}
23+
steps:
24+
- uses: actions/checkout@v6
25+
26+
- id: filter
27+
if: github.event_name == 'push'
28+
uses: dorny/paths-filter@v4
29+
with:
30+
filters: |
31+
frontend:
32+
- 'frontend/**'
33+
- 'pnpm-lock.yaml'
34+
- 'pnpm-workspace.yaml'
35+
backend:
36+
- 'backend/**'
37+
- 'pnpm-lock.yaml'
38+
- 'pnpm-workspace.yaml'
39+
40+
- id: out
41+
run: |
42+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
43+
echo "frontend=true" >> "$GITHUB_OUTPUT"
44+
echo "backend=true" >> "$GITHUB_OUTPUT"
45+
else
46+
echo "frontend=${{ steps.filter.outputs.frontend }}" >> "$GITHUB_OUTPUT"
47+
echo "backend=${{ steps.filter.outputs.backend }}" >> "$GITHUB_OUTPUT"
48+
fi
49+
50+
build-frontend:
51+
needs: resolve
52+
if: needs.resolve.outputs.frontend == 'true'
53+
uses: ./.github/workflows/_build-image.yml
54+
with:
55+
component: frontend
56+
dockerfile: ./frontend/Dockerfile
57+
image: ${{ github.repository_owner }}/greedyreader-fe
58+
version: ${{ github.run_number }}
59+
no_cache: ${{ inputs.no_cache || false }}
60+
secrets:
61+
BUILD_ENV: ${{ secrets.FRONTEND_ENV }}
62+
63+
build-backend:
64+
needs: resolve
65+
if: needs.resolve.outputs.backend == 'true'
66+
uses: ./.github/workflows/_build-image.yml
67+
with:
68+
component: backend
69+
dockerfile: ./backend/Dockerfile
70+
image: ${{ github.repository_owner }}/greedyreader-be
71+
version: ${{ github.run_number }}
72+
no_cache: ${{ inputs.no_cache || false }}
73+
74+
deploy:
75+
needs: [resolve, build-frontend, build-backend]
76+
if: |
77+
always() &&
78+
!cancelled() &&
79+
needs.resolve.result == 'success' &&
80+
needs.build-frontend.result != 'failure' &&
81+
needs.build-backend.result != 'failure' &&
82+
(needs.resolve.outputs.frontend == 'true' || needs.resolve.outputs.backend == 'true')
83+
uses: ./.github/workflows/_deploy-compose.yml
84+
with:
85+
frontend_tag: ${{ needs.resolve.outputs.frontend == 'true' && github.run_number || 'latest' }}
86+
backend_tag: ${{ needs.resolve.outputs.backend == 'true' && github.run_number || 'latest' }}
87+
secrets:
88+
BACKEND_ENV: ${{ secrets.BACKEND_ENV }}

backend/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
FROM node:22-alpine AS base
2+
RUN corepack enable
3+
WORKDIR /repo
4+
5+
FROM base AS deps
6+
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
7+
COPY backend/package.json backend/package.json
8+
RUN pnpm fetch
9+
10+
FROM deps AS build
11+
COPY backend backend
12+
RUN pnpm install --frozen-lockfile --filter @greedy-reader/backend
13+
RUN pnpm --filter @greedy-reader/backend run build
14+
15+
FROM node:22-alpine AS runtime
16+
RUN corepack enable
17+
WORKDIR /app
18+
ENV NODE_ENV=production
19+
20+
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
21+
COPY backend/package.json backend/package.json
22+
RUN pnpm install --prod --frozen-lockfile --filter @greedy-reader/backend
23+
24+
COPY --from=build /repo/backend/dist backend/dist
25+
26+
RUN mkdir -p /app/data
27+
WORKDIR /app/data
28+
29+
EXPOSE 8000
30+
CMD ["node", "/app/backend/dist/index.js"]

docker-compose.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
services:
2+
backend:
3+
image: ghcr.io/greedylabs/greedyreader-be:${BACKEND_TAG:-latest}
4+
container_name: greedyreader-be
5+
restart: unless-stopped
6+
env_file:
7+
- backend.env
8+
volumes:
9+
- backend-data:/app/data
10+
ports:
11+
- "8002:8000"
12+
13+
frontend:
14+
image: ghcr.io/greedylabs/greedyreader-fe:${FRONTEND_TAG:-latest}
15+
container_name: greedyreader-fe
16+
restart: unless-stopped
17+
ports:
18+
- "3002:80"
19+
depends_on:
20+
- backend
21+
22+
volumes:
23+
backend-data:

frontend/Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM node:22-alpine AS base
2+
RUN corepack enable
3+
WORKDIR /repo
4+
5+
FROM base AS deps
6+
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
7+
COPY frontend/package.json frontend/package.json
8+
RUN pnpm fetch
9+
10+
FROM deps AS build
11+
COPY frontend frontend
12+
RUN pnpm install --frozen-lockfile --filter @greedy-reader/frontend
13+
RUN --mount=type=secret,id=build_env,target=/repo/frontend/.env \
14+
pnpm --filter @greedy-reader/frontend run build
15+
16+
FROM nginx:1.27-alpine AS runtime
17+
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
18+
COPY --from=build /repo/frontend/dist /usr/share/nginx/html
19+
EXPOSE 80

frontend/nginx.conf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
server {
2+
listen 80;
3+
server_name _;
4+
root /usr/share/nginx/html;
5+
index index.html;
6+
7+
gzip on;
8+
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
9+
gzip_min_length 1024;
10+
11+
location /assets/ {
12+
expires 1y;
13+
add_header Cache-Control "public, immutable";
14+
try_files $uri =404;
15+
}
16+
17+
location / {
18+
try_files $uri $uri/ /index.html;
19+
}
20+
}

0 commit comments

Comments
 (0)