1인 개발자가 사이드 프로젝트를 운영 서버에 배포하기까지의 여정

문제: "배포할 때마다 5분씩 기다려야 해"

사이드 프로젝트 Contents Hub를 운영하면서 배포가 점점 귀찮아졌다. 매번 rsync로 코드 올리고, VM에서 docker build 하면 5분. 작은 수정 하나에도 커피 한 잔 마시고 와야 했다.

# 기존 배포 절차 (고통)
rsync -avz ./backend/ server:/app/     # 30초
ssh server "docker build . -t app"      # 3-5분 (VM이 느려서...)
ssh server "docker-compose up -d"       # 10초

특히 VM 스펙이 낮아서 빌드가 느린 게 문제였다. 내 Mac에서는 30초면 되는 빌드가 VM에서는 5분.

해결: "로컬에서 빌드하고, 이미지만 올리자"

Docker 이미지 레지스트리를 쓰면 된다는 걸 알고 있었지만, "Docker Hub는 무료 플랜이 제한적이고, AWS ECR은 설정이 복잡하고..." 하면서 미뤄왔다.

그러다 **GHCR (GitHub Container Registry)**를 발견했다.

GHCR을 선택한 이유

기능 Docker Hub GHCR
Private 이미지 유료 ($5/월) 무료
Pull 제한 100회/6시간 없음
GitHub Actions 연동 별도 설정 네이티브

1인 개발자에게 GHCR은 거의 완벽한 선택이었다.

첫 번째 삽질: ARM64 vs AMD64

Mac에서 빌드한 이미지를 VM에서 실행하니...

backend The requested image's platform (linux/arm64) does not match
the detected host platform (linux/amd64/v4)

M1 Mac은 ARM64, 대부분의 클라우드 VM은 AMD64. 플랫폼이 다르다.

해결책: docker buildx로 크로스 플랫폼 빌드

docker buildx build --platform linux/amd64 \
  -t ghcr.io/myuser/myapp:v1.0.0 \
  --push .

--platform linux/amd64를 붙이면 Mac에서도 Linux AMD64용 이미지를 빌드할 수 있다.

두 번째 삽질: GHCR 권한

gh auth login으로 GitHub 인증을 했는데 push가 안 된다.

denied: permission_denied: The token provided does not match expected scopes.

원인: GitHub CLI의 기본 인증에는 write:packages 권한이 없다.

해결책:

gh auth refresh -s write:packages

브라우저에서 추가 권한을 승인하면 된다.

세 번째 삽질: .env 변경이 안 먹혀

.env에서 Redis URL을 수정하고 docker-compose restart를 했는데, 여전히 옛날 설정을 사용한다.

원인: restart는 컨테이너를 재시작만 하고, 환경변수는 컨테이너 생성 시점에 주입된다.

해결책:

# restart 대신
docker-compose down && docker-compose up -d

# 또는
docker-compose up -d --force-recreate

완성된 배포 스크립트

삽질 끝에 완성한 원클릭 배포 스크립트:

#!/bin/bash
# scripts/deploy.sh
set -e

VERSION=${1:-latest}
IMAGE="ghcr.io/myuser/myapp"

echo "🧪 테스트 실행 중..."
pytest tests/ -v --tb=short -q

echo "🏗️ Docker 이미지 빌드 중 (linux/amd64)..."
docker buildx build --platform linux/amd64 \
    -t "$IMAGE:$VERSION" \
    -t "$IMAGE:latest" \
    --push .

echo "📦 VM에 배포 중..."
ssh server "
    docker pull $IMAGE:$VERSION
    docker-compose -f docker-compose.prod.yml up -d --force-recreate
"

echo "✅ 배포 완료! ($VERSION)"

이제 배포는:

./scripts/deploy.sh v1.0.1

20초면 끝난다. 테스트 → 빌드 → 푸시 → 배포가 자동으로.

핵심 교훈

1. 빌드는 로컬에서, 실행만 서버에서

VM에서 빌드하는 건 시간 낭비다. 로컬에서 빌드하고 이미지만 pull하면 된다.

2. 크로스 플랫폼은 필수 고려사항

M1/M2 Mac을 쓴다면 --platform linux/amd64를 습관처럼 붙이자.

3. 실패하면 다음 단계로 가지 마라

set -e로 스크립트가 실패 시 즉시 중단되게. 테스트 실패했는데 배포되는 사고를 막는다.

4. 버전 태그를 남겨라

latest만 쓰면 롤백이 어렵다. v1.0.0, v1.0.1 식으로 태그를 남기면 문제 발생 시:

docker pull ghcr.io/myuser/myapp:v1.0.0  # 이전 버전으로 롤백

다음 단계: GitHub Actions

지금은 ./scripts/deploy.sh를 수동으로 실행하지만, 다음 목표는 GitHub Actions로 완전 자동화다:

# .github/workflows/deploy.yml
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Build and push
        run: |
          docker buildx build --platform linux/amd64 \
            -t ghcr.io/${{ github.repository }}:${{ github.sha }} \
            --push .

main에 푸시하면 자동 배포. 꿈의 CI/CD다.


1인 개발자도 DevOps 할 수 있다. 복잡할 것 없다. 한 단계씩.