📌 목차

이번에 기존 Express기반 프로젝트를 NestJS로 마이그레이션하면서, Monorepo 구조로 서비스를 재정비하고 있었다. 서비스가 여러 컨테이너로 나뉘어 있다 보니, 매번 수동으로 docker build -> tag -> push 과정을 반복해야 했다. 그러다 이 폴더, 저 폴더 오가며 수동 배포하는 시간이 너무나 비효율적으로 느껴졌다. 사실 이전에 자동화하고 싶기도 했지만 러닝커브가 있을 거란 생각에 망설인 점도 있었다. 그러던 중, Github Actions를 활용해 해당 과정을 자동화한 케이스를 발견했고, 직접 테스트해본 결과 예상보다 훨씬 간단하게 적용할 수 있었다.
결과적으로 이전에 비해 훨씬 편해졌다.. 비슷한 고민을 하고 있는 개발자들에게 도움이 되길 바라는 마음으로 글을 작성하게 되었다.
GitHub Actions Workflow
우선, Actions에 Docker image 관련 config가 존재해서 해당 config를 커스텀해도 좋다. 하지만 나는 수동으로 진행했다.
필요한 기능이 정말 딱 이미지 빌드와 배포만이었기에 직접 작성했다.
전체 코드를 먼저 보고, 하나씩 뜯어보는 시간을 갖자!



나는 상단에 보이는 set up a workflow yourself -> 를 눌러서 수동으로 등록했다. configure를 눌러서 필요한 부분만 지워도 괜찮음.

적용한 워크플로우 코드
# .github/workflows/nami-docker.yml
name: Build and Push Nami Docker Image
on:
push:
branches:
- main
paths:
- 'apps/nami/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set Docker Image Tag
id: tag
run: echo "value=$(date +%Y%m%d)-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
- name: Create .env file
run: echo "${{ secrets.NAMI_ENV }}" > apps/nami/.env
- name: Build and Push Nami Image
uses: docker/build-push-action@v5
with:
context: .
file: apps/nami/Dockerfile
push: true
tags: yourdockerid/nami:${{ steps.tag.outputs.value }}
복붙해서 써도 되지만 설정이 필요하기에 하나씩 뜯어보는 것을 추천한다! (secrets 설정도 해주어야하기에)
하나씩 뜯어보기 📚
이번에 만든 nami-docker.yml 파일을 기준으로 각 단계가 어떤 역할을 하는지, 왜 필요한지를 함께 정리해보겠다.
on - 워크플로우 실행 시점 설정하기
on:
push:
branches:
- main
paths:
- 'apps/nami/**'
workflow_dispatch:
push : main 브랜치에 apps/nami 폴더 내부 변경사항이 생겼을 때만 워크플로우가 실행된다.
paths : 전체 프로젝트가 아닌 특정 서비스 변경에만 반응한다.
자세히 설명하자면, 지정된 경로 아래의 파일이 변경된 경우에만 워크플로우를 실행하도록 제한한다. 예를 들어 apps/base/나 libs/ 디렉토리에 변경이 생겨도 이 워크플로우는 실행되지 않으며, 오직 apps/nami/ 내부의 코드, 설정, Dockerfile 등이 수정된 경우에만 트리거된다.
특히 모노레포 구조에서 서비스 단위로 효율적인 자동화를 구축할 때 이 설정은 정말 효율적이다. 변경되지 않은 서비스는 아예 건드리지 않기 때문에 빌드/배포 시간도 줄어들고 리소스 낭비도 없다.
💭 정말 정말 편한 점이다. 이전엔 서비스별로 수동 배포하면서 폴더 왔다 갔다했는데... 이제는 paths를 설정해두면 원하는 서비스만 자동으로 배포되는 것이다. 혼자 감동함.... 왜 이제 적용했나 싶었다.
workflow_dispatch : 이 설정을 추가하면 Github 웹에서 직접 수동 실행할 수 있는 버튼이 생긴다.

💭 실패했을 때 재실행해보거나 굳이 파일을 수정하지 않고도 동작을 확인할 수 있어서 테스트할 때 유용할 것 같아서 추가했다.
jobs.build - 실제 작업 정의하기
jobs:
build:
runs-on: ubuntu-latest
jobs는 이 워크플로우에서 어떤 작업을 할지 정의하는 구역이다. build는 Job의 이름(key)이라 수정할 수 있다. Docker build와 push를 담당해서 build라고 해놓았다.
runs-on: ubuntu-latest는 실행될 가상 머신 환경을 지정한다. 최신 Ubuntu 환경의 runner를 띄워서 우리가 정의한 스크립트를 순차적으로 실행해주는 거라 보면된다.
steps - 수행할 작업들의 순서
steps:
- name: Checkout code
uses: actions/checkout@v4
name은 생략할 수 있다. Github Actions 실행 화면에서 해당 step의 이름으로 표시되는 부분이라 꼭 넣을 필요는 없지만 가독성, 디버깅 면에서 유용해서 있으면 좋다. 이 순서에서 무엇을 하는지 파악할 수 있다.
actions/checkout 액션은 현재 Github 레포의 소스 코드를 해당 runner 환경으로 clone해주는 역할을 한다. 코드가 runner 환경에 복사되어야만 Docker build도 가능하고 yml에 정의된 경로들도 인식할 수 있다.
Docker Build & Push 단계
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
빌드 환경을 준비하는 부분이라고 보면 된다.
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
DockerHub 로그인 단계.
다만 username과 password를 보면 secrets를 사용한 것을 알 수 있다. 자동화 워크플로우에서 DockerHub 로그인 정보나 .env 환경 변수 등을 외부에 노출되면 안되는 민감한 데이터기 때문에 Github 레포 설정에서 Secrets를 따로 설정해주었다.
GitHub Secrets는 GitHub에서 제공하는 보안 저장소로, 민감한 정보를 안전하게 저장하고 워크플로우에서만 사용할 수 있다.

단! 중요한 것은 Docker Password에 진짜 비밀번호를 넣는 건 권장하지 않는다. Access Token을 만들어서 사용하자!
DockerHub → [Security Settings] → [New Access Token]
여기서 생성한 토큰을 Secrets에 등록해서 사용하는 방식이 훨씬 안전하고, Docker에서 로그 추적도 가능하다.
기간을 설정할 수 있고, 필요할 때 만료/폐기할 수 있다는 장점까지..!
- name: Set Docker Image Tag
id: tag
run: echo "value=$(date +%Y%m%d)-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
20250328-abc1234 처럼 빌드 날짜 + 커밋 해시 조합으로 이미지 태그를 생성하는 부분이다.
이 부분도 원래 태그를 latest라고 지정했었는데 다음과 같은 문제에 부딪혔다.
1. 정확한 버전 관리가 어렵다. 어떤 커밋을 기준으로 만들어졌는지 파악하기 힘들다.
2. 캐시된 이미지로 인해 pull했을 때 최신 이미지를 업데이트 못할 수도 있다는 단점
3. 그렇다고 --no-cache 옵션 설정해서 push하기엔 빌드 시간이 매번 오래 걸려서 비효율적이다.
그래서 선택한 방식은, 유니크한 태그를 생성하고 스스로 어느 정도 작업이 완료되었을 때, 별도로 안정성이 확보된 시점에만 latest 버전을 수동 push 해주는 것으로 결정했다.
정리하자면, workflow 태깅은 "날짜-SHA" 조합으로, 실제 운영 배포시에는 필요한 시점에만 latest를 수동 관리한다.
- name: Create .env file
run: echo "${{ secrets.NAMI_ENV }}" > apps/nami/.env
CI 자동화 과정에서 처음엔 .env 파일이 누락되어 빌드 오류가 발생했다. 이는 보안을 위해 .env 파일은 .dockerignore에 포함시켜 두었기 때문이다. 이 문제를 해결하기 위해 Secrets에 등록하고 워크플로우 실행 중 동적으로 생성하는 방식으로 변경했다.
- name: Build and Push Nami Image
uses: docker/build-push-action@v5
with:
context: .
file: apps/nami/Dockerfile
push: true
tags: yourdockerid/nami:${{ steps.tag.outputs.value }}
드디어 실제 docker image build & push 단계.
context: 빌드 기준 디렉토리
file: Dockerfile 경로
push: true Build한 이미지를 실제로 DockerHub에 올리기
tags: 우리가 만든 커스텀 태그로 등록
💭 추가로 내가 추후에 적용해보고 싶은 부분은 모노레포 구조니까, 전부 마이그레이션 된다면 최상위 루트에 docker-compose.yml 파일을 두었을테니 위의 해당 커스텀 태그를 compose 파일에도 자동으로 push하여 반영하도록 하고 싶다. 그렇다면 진정한 자동화 완성이지 않을까 싶음.
마무리
이렇게 단순히 템플릿 복붙이 아닌, 내 프로젝트에 맞춘 커스터마이징된 workflow를 만들어서 적용해보았다. 작은 자동화 하나로 개발 생산성을 물론, 협업 안정성까지 확보할 수 있었다. 나는 특히 이번에 모노레포 구조를 도입하면서 앞으로는 Github Actions를 적극적으로 활용해야겠다는 확신이 생겼다.
아직 백엔드를 접한지 얼마 되지 않았지만 전체 흐름을 이해하고 디테일한 부분을 하나씩 챙겨가는 나의 모습이 스스로 느껴졌다. 성장하고 있다는 실감.
예전에 Github Actions를 처음 접했을 땐, 너무 어려워서 여러 번 디버깅했던 기억이 있다. N년차 개발자들의 능숙한 솜씨에 기죽었던 적도 많다.
하지만 이렇게 직접 부딪혀보며 하나씩 해내다 보면, 자연스럽게 지금 필요한 개념이 정리되고 해야할 일이 무엇인지 명확해진다. 배움을 꾸준히 유지하다보면 이런 날이 오는 것 같다. 요즘 개발하는 매 순간이 꽤 즐겁다. 앞으로도 그 마음을 잃지 않기를..!
'Development Environment' 카테고리의 다른 글
| pnpm은 왜 Lifecycle module을 v10에서 차단했을까? (0) | 2025.03.15 |
|---|---|
| Docker Compose V2에서 .env를 못 찾는다면? (0) | 2025.03.02 |
| Webpack의 Module Federation 동작 흐름 그리고 Dynamic import (1) | 2024.11.23 |
| 2016, Left-pad 사건에 대한 Rich Harris(Rollup)의 글로 알아보는 번들링의 중요성 (4) | 2024.08.17 |
| Why Vite? JS 모듈화의 역사, CJS, ESM, Webpack (3) | 2024.08.10 |