Automating Magento Deployment — Why I Dropped GitHub Actions for a Direct Mac→SSH Setup
With a manual trigger and no main repo, GitHub Actions is over-engineering. Running the server's deploy.sh directly via ssh -t from Raycast, plus avoiding the maintenance-mode lockout with trap and the composer install pitfall.
A record of automating Magento + Hyvä deployment: I seriously evaluated GitHub Actions, dropped it, and settled on running deploys directly over SSH from my Mac. If you have a manual trigger and no main repo, this is the right shape.
TL;DR
[맥] Raycast 버튼 ──ssh -t (직결)──▶ [서버] deploy.sh → 각 모듈/테마 git pull → 빌드
# 맥: Raycast Script Command 한 줄 (사이트는 드롭다운)
ssh -t "$SSH_HOST" "cd '$REMOTE_PATH' && ./deploy.sh"# 서버 deploy.sh — 핵심 2가지
# 1) trap으로 실패해도 점검모드 자동 해제
cleanup() {
local code=$?
if [ "$code" -ne 0 ]; then
echo "✖ 실패(exit $code) — 유지보수 모드 해제"
bin/magento maintenance:disable || true
fi
}
trap cleanup EXIT
# 2) 모듈/테마는 배열 하나가 단일 레지스트리. 새 모듈은 여기 한 줄 추가
GIT_DIRS=(
"app/code/Yohan/ModuleA"
"app/code/Yohan/ModuleB"
"app/design/frontend/Hyva/Hyva_K-SALE"
)
for d in "${GIT_DIRS[@]}"; do git -C "$d" pull --ff-only; doneThe deploy command chain uses composer install (lock as-is, reproducible), not composer update. No GitHub Secrets, deploy keys, or workflow YAML at all.
Why I dropped GitHub Actions
A single deploy is this long chain: maintenance:enable → composer install → setup:upgrade → di:compile → tailwind build → clean static → static-content:deploy (9 locales) → cache:flush → maintenance:disable. Too long to type by hand, so automation is a must. The candidates were GitHub Actions vs an SSH script (a Mac app was out — a Raycast alias replaces it in a second), and for a while I leaned toward the flashier Actions, then dropped it.
- I have no main repo. It's not the Magento root but custom modules, each its own git repo. "Which repo's push do I trigger on?" is ambiguous from the start, and there's nowhere obvious to put the YAML.
- No approval gate, no auto-trigger needed. Of what Actions offers (push auto-trigger, approval gate, central log), none apply here. The trigger is intentionally manual anyway.
- Then GitHub is just a "relay that runs one SSH line for you." Issuing a deploy key, registering Secrets, and agonizing over the YAML location for that relay is pure overhead — like building an approval system for a single manual deploy.
Two pitfalls
- A
&&chain locks the site in maintenance mode. Symptom: one middle step fails (especially the 9-locale static deploy) → the chain stops andmaintenance:disablenever runs → the site is stuck on the maintenance screen. Fix: puttrap cleanup EXIT(see TL;DR) in the script so maintenance is released on exit even on failure. composer updateis dangerous in production. Symptom: update bumps every dependency within the composer.json constraints to the latest, so core and third-party can change without warning. Fix: reproduciblecomposer install(lock pinned). To bump only your own modules, target them:composer update yohan/* --with-dependencies.
The workflow doesn't live per module
The deploy "trigger" isn't tied to the source repo. The thing that git-pulls every module is a single deploy.sh on the server, and its GIT_DIRS array is the single registry saying "this site is made of these modules + themes." Ten modules, still one trigger; a new module is one line added to the array. The individual module repos are just code stores — no CI, no workflow needed.
So when do you use GitHub Actions
I'll adopt it when one of two conditions appears: when multiple people are involved in deploys and you need a record/approval of "who deployed when," or when you want a pipeline that auto-deploys to staging on push. Until then, direct is the answer.
Lessons
- A trendy tool isn't the right answer for your situation. If you don't have the problem it solves (team collaboration, auto-trigger, traceability), it's pure cost.
- Before following "the standard," ask whether the problem that standard solves is one you actually have. Over-engineering — bringing a tool to a problem you don't have — is the biggest enemy.
- Just moving a hand-typed
&&chain into a script (with trap) prevents one maintenance-mode lockout.