Magento 배포 자동화 — GitHub Actions를 검토했다가 접고 Mac→SSH 직결로 간 이유
유행하는 GitHub Actions를 진지하게 검토했다가 접고, Mac에서 SSH로 직접 배포 스크립트를 돌리는 구조로 정착한 기록. 수동 트리거 + 메인 레포 없음 조합에서의 선택.
Magento + Hyvä 사이트를 운영하다 보면 코드를 고칠 때마다 이 긴 명령어를 친다.
maintenance:enable → composer install → setup:upgrade → setup:di:compile
→ tailwind build → static 정리 → static-content:deploy (로케일 9개)
→ cache:flush → maintenance:disable
모듈/테마 git pull까지 앞에 붙으면 한 화면을 넘긴다. 자주 해야 하는 일이 이렇게 길고 손으로 치는 거라면, 자동화는 선택이 아니라 필수다. 이 글은 그 자동화를 어떻게 설계했는지 — 정확히는 유행하는 방법(GitHub Actions)을 진지하게 검토했다가 왜 접었는지에 대한 기록이다.
후보들
처음 떠오른 선택지는 셋이었다. GitHub Actions로 push 트리거 배포, macOS 앱 개발, 그냥 SSH 스크립트.
macOS 앱은 빨리 접었다. 버튼 하나 누르려고 앱을 만드는 건 셸 alias나 Raycast가 1초 만에 대체한다. 남은 건 GitHub Actions vs 스크립트였고, 한동안 GitHub Actions 쪽으로 기울었다. 멋있어 보였으니까.
GitHub Actions를 접은 이유
설계를 구체화하다 보니 내 상황과 안 맞는 지점이 드러났다.
첫째, 나는 메인 레포가 없다. Magento 루트가 하나의 레포가 아니라 커스텀 모듈들이 각각 따로 git 레포로 흩어져 있다. 그러면 "어느 레포 push에 트리거를 걸지?"부터 애매하다. 워크플로우 YAML을 어디 둘지도 정해지지 않는다.
둘째, 이 배포에는 승인 게이트도, 자동 트리거도 필요 없다. GitHub Actions가 주는 가치 — push 자동 트리거, 승인 게이트, 중앙 배포 로그 — 중에 이 워크플로우에 필요한 게 없다. 거쳐야 할 승인 단계가 없고, 트리거도 어차피 의도적으로 수동이다.
셋째, 그러면 GitHub은 결국 "SSH 명령 한 줄을 대신 실행해주는 중계소" 역할밖에 안 한다. 그 중계를 위해 SSH 배포 키를 새로 발급하고, GitHub Secrets를 등록하고, 워크플로우 파일 위치를 고민하는 건 — 순수 오버헤드다. 수동 배포 하나에 결재 시스템을 만드는 격이었다.
그래서 GitHub을 통째로 빼기로 했다. 흐름이 이렇게 단순해진다.
[맥] Raycast 버튼
│ ssh -t (직결)
▼
[서버] deploy.sh → 각 모듈/테마 git pull → 빌드
가다가 배운 함정 두 개
1) && 체인은 사이트를 점검 모드에 가둔다
원래 명령어는 maintenance:enable && composer install && ... && maintenance:disable 형태였다. 문제는, 중간 단계(특히 로케일 9개 도는 static deploy)가 하나라도 실패하면 체인이 거기서 멈추고 maintenance:disable이 영영 실행되지 않는다. 사이트가 점검 화면에 갇힌다.
해결은 스크립트에 trap을 거는 것. 실패하든 말든 종료 시 점검 모드를 해제한다.
cleanup() {
local code=$?
if [ "$code" -ne 0 ]; then
echo "✖ 실패(exit $code) — 유지보수 모드 해제"
bin/magento maintenance:disable || true
fi
}
trap cleanup EXIT손으로 치던 && 체인에는 이런 안전장치가 없다. 스크립트로 옮기는 것만으로 사고 하나를 막는다.
2) 프로덕션에서 composer update는 위험하다
원래 composer update를 썼다. 그런데 update는 composer.json 제약 범위 안에서 의존성을 전부 최신으로 올린다 — 코어나 서드파티 모듈이 예고 없이 바뀔 수 있다. 재현 가능한 배포는 composer install(lock 그대로)이 맞다. 본인 모듈만 새 버전을 받아야 하면 전체 update 말고 composer update yohan/* --with-dependencies처럼 타겟팅한다.
워크플로우는 모듈마다 두는 게 아니다
이건 개념을 한 번 정리하니 명확해졌다. 배포 "트리거"는 소스 코드 레포와 묶여 있지 않다. 실제로 모든 모듈을 git pull 하는 주체는 서버의 deploy.sh 하나다. 그 안의 배열이 "이 사이트는 이 모듈들 + 이 테마들로 구성된다"는 단일 레지스트리 역할을 한다.
GIT_DIRS=(
"app/code/Yohan/ModuleA"
"app/code/Yohan/ModuleB"
"app/design/frontend/Hyva/Hyva_K-SALE"
"app/design/frontend/Hyva/Hyva_K-SALE-2"
)
for d in "${GIT_DIRS[@]}"; do git -C "$d" pull --ff-only; done모듈이 10개여도 트리거는 1개. 새 모듈은 이 배열에 한 줄 추가하면 끝이다. 개별 모듈 레포는 그냥 코드 저장소일 뿐, CI도 워크플로우도 필요 없다.
최종 형태
맥에는 Raycast Script Command 하나. 사이트를 드롭다운으로 고르면 해당 서버로 ssh -t(진행 로그 실시간) 해서 deploy.sh를 실행한다.
ssh -t "$SSH_HOST" "cd '$REMOTE_PATH' && ./deploy.sh"서버에는 trap + 로깅을 갖춘 deploy.sh 하나. 테마가 여러 개면 tailwind 빌드를 배열로 돌리고, static 정리는 pub/static/frontend/Hyva를 통째로 지워 테마 수와 무관하게 만든다.
준비물은 "맥에서 그 서버로 SSH가 되는 것"뿐. 이미 VS Code Remote-SSH를 쓰고 있어서 키는 있었다. GitHub Secrets도, 배포 키 발급도, 워크플로우 YAML도 전부 사라졌다.
그래서 GitHub Actions는 언제 쓰나
영영 안 쓴다는 게 아니다. 두 조건 중 하나가 생기면 그때 도입하는 게 맞다. 배포에 여러 사람이 관여해서 "누가 언제 배포했는지" 기록·승인이 필요할 때, 또는 push하면 스테이징에 자동 배포되는 파이프라인을 원할 때. 그 전까지 수동 트리거 + 메인 레포 없음 조합에선 직결이 정답이다.
교훈
유행하는 도구가 내 상황의 정답은 아니다. GitHub Actions, CI/CD, 빌드 아티팩트 — 다 좋은 것들이지만 그게 해결하는 문제(팀 협업, 자동 트리거, 추적성)를 내가 안 갖고 있으면 순수 비용이다. "정석"을 따르기 전에 그 정석이 푸는 문제가 내게 있는지부터 물어야 한다. 필요 없는 문제에 도구를 들이는 것, 즉 오버엔지니어링이 가장 큰 적이다.