킹서버에 `vite dev` 가 떠 있었다. 요청마다 트랜스파일하고, 컨테이너 기동이 npm install 네트워크에 의존하고, /src 원본과 소스맵이 그대로 나간다. 발행물을 파는 사이트의 진입점이 dev 서버일 이유가 없다. - nginx/Dockerfile 신규: @o2o/frontend 를 굽는 build 스테이지 + 번들을 담은 nginx. 발행 사이트는 이미지에 안 넣는다 — 사이트가 늘 때마다 이미지를 다시 굽지 않으려고 site-out 볼륨에서 읽는다. - vite.config.ts: build.assetsDir='builder-assets'. 발행본과 같은 오리진이라 `/assets/` 를 서로 뺏는다 — 안 가르면 빌더 JS·CSS 가 404 인데 화면은 떠서 원인이 안 보인다. - site.conf: `/` 를 /srv/app 정적으로. index.html 은 no-cache — 번들 해시가 여기 박혀 있어 캐시되면 재배포해도 옛 번들 주소를 계속 부른다. `/fonts/` 는 발행본 먼저 보고 없으면 빌더로 떨어뜨린다(둘이 같은 경로를 각자 쓴다). - compose: solution-frontend(dev 서버)를 profile dev 로 내리고, 굽기만 하는 solution-prerender 를 기본으로 올린다. VITE_* 는 번들에 구워지므로 build args 다 — 주소를 바꾸면 재빌드해야 한다. docker compose config 통과
128 lines
5.6 KiB
Bash
Executable File
128 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 배포 — 코드를 당기고, 지정한 서비스만 다시 빌드해 갈아끼운다.
|
|
#
|
|
# ./deploy.sh 전체
|
|
# ./deploy.sh solution-backend 그 서비스만
|
|
# ./deploy.sh solution-backend solution-worker 여럿
|
|
#
|
|
# 서비스명 대신 컨테이너명(o2o-web4ai-solution-backend)으로 불러도 받는다.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
|
|
PREFIX=o2o-web4ai
|
|
# api·worker·api-admin 은 이미지 한 벌(o2o-web4ai-backend)을 나눠 쓴다.
|
|
BACKEND_SVCS=(solution-backend solution-worker admin-backend)
|
|
BRANCH=${DEPLOY_BRANCH:-main}
|
|
|
|
PULL=1
|
|
ONLY=0
|
|
TARGETS=()
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
사용법: ./deploy.sh [옵션] [서비스...]
|
|
|
|
옵션
|
|
--no-pull 코드를 당기지 않는다(디스크에 있는 코드 그대로 빌드)
|
|
--only 백엔드 형제 서비스를 함께 갈아끼우지 않는다 (아래 ★ 참고)
|
|
-h, --help
|
|
|
|
환경변수
|
|
DEPLOY_BRANCH 기본 main. 다른 브랜치를 배포할 때만 쓴다
|
|
|
|
서비스: solution-backend · solution-worker · solution-prerender · solution-site
|
|
admin-backend · admin-frontend (프로필 admin, 기본 기동에서 빠져 있다)
|
|
solution-frontend (프로필 dev, 로컬 HMR 전용 — 운영에서 띄우지 않는다)
|
|
o2o-web4ai-solution-backend 처럼 컨테이너명으로 적어도 된다
|
|
|
|
★ solution-backend·solution-worker·admin-backend 는 이미지가 한 벌이다. 하나를 빌드하면 나머지도 새 이미지로
|
|
갈아끼워야 한다 — 안 그러면 옛 코드로 도는 컨테이너가 남는데, 셋 다 "살아 있음" 이라
|
|
화면상으로는 배포가 끝난 것처럼 보인다. --only 는 그걸 알고 건너뛸 때만 쓴다.
|
|
USAGE
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--no-pull) PULL=0 ;;
|
|
--only) ONLY=1 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
-*) echo "모르는 옵션: $1" >&2; usage >&2; exit 2 ;;
|
|
*) TARGETS+=("${1#"$PREFIX"-}") ;; # 컨테이너명으로 불러도 받는다
|
|
esac
|
|
shift
|
|
done
|
|
|
|
ALL_SVCS=$(docker compose config --services)
|
|
for t in ${TARGETS+"${TARGETS[@]}"}; do
|
|
grep -qx "$t" <<<"$ALL_SVCS" || {
|
|
echo "그런 서비스가 없다: $t" >&2
|
|
echo "있는 것: $(tr '\n' ' ' <<<"$ALL_SVCS")" >&2
|
|
exit 2
|
|
}
|
|
done
|
|
|
|
# ── 코드 ────────────────────────────────────────────────────────────
|
|
# 배포 서버의 작업트리는 main 의 **사본**이지 작업 공간이 아니다. 그래서 머지(pull)가 아니라
|
|
# origin/$BRANCH 로 하드 리셋한다 — 밖에서 force-push 가 나도 --ff-only 로 막히지 않고,
|
|
# 서버에서 누가 손댄 흔적이 다음 배포까지 살아남지 않는다.
|
|
#
|
|
# ★ fetch 가 실패하면 **리셋하지 않는다.** 이 서버엔 gitea 자격증명이 없어 fetch 가 죽는데,
|
|
# 그 상태의 origin/$BRANCH 는 마지막으로 fetch 된 낡은 ref 다.
|
|
# 실측(2026-09-01 킹서버): HEAD=9b4fe40 인데 origin/main=4871e50 — 믿고 리셋하면 한 커밋
|
|
# 롤백된다. 그러고도 빌드는 성공하고 컨테이너는 뜬다. 조용히 틀리는 종류다.
|
|
#
|
|
# ★ git clean 은 하지 않는다. .env 와 nginx/site.conf 는 추적되지 않는 파일이고 서버마다
|
|
# 다르다 — reset --hard 는 이 둘을 건드리지 않지만 clean 은 지운다.
|
|
if [ "$PULL" = 1 ]; then
|
|
echo "▶ git fetch origin $BRANCH"
|
|
if git fetch --prune origin "$BRANCH" 2>&1; then
|
|
DIRTY=$(git status --porcelain)
|
|
if [ -n "$DIRTY" ]; then
|
|
echo " ! 작업트리 변경을 버린다:"
|
|
sed 's/^/ /' <<<"$DIRTY"
|
|
fi
|
|
echo "▶ git reset --hard origin/$BRANCH"
|
|
git reset --hard "origin/$BRANCH"
|
|
else
|
|
echo " ! fetch 실패 — 리셋을 건너뛰고 디스크에 있는 코드 그대로 간다." >&2
|
|
echo " origin/$BRANCH 가 낡았을 수 있어 그걸로 리셋하면 배포가 조용히 롤백된다." >&2
|
|
echo " (밖에서 밀어넣었으면 이대로 두면 되고, 자동화하려면 gitea 배포키를 건다)" >&2
|
|
fi
|
|
fi
|
|
echo "▶ 지금 코드: $(git log --oneline -1)"
|
|
|
|
# ── 대상 정하기 ─────────────────────────────────────────────────────
|
|
if [ ${#TARGETS[@]} -eq 0 ]; then
|
|
SVCS=() # 빈 인자 = compose 가 전부로 해석한다
|
|
echo "▶ 대상: 전체"
|
|
else
|
|
SVCS=("${TARGETS[@]}")
|
|
# 백엔드 하나를 건드리면 같은 이미지를 쓰는 형제도 함께 갈아끼운다.
|
|
if [ "$ONLY" = 0 ]; then
|
|
for t in "${TARGETS[@]}"; do
|
|
for b in "${BACKEND_SVCS[@]}"; do [ "$t" = "$b" ] || continue
|
|
for sib in "${BACKEND_SVCS[@]}"; do
|
|
printf '%s\n' "${SVCS[@]}" | grep -qx "$sib" || {
|
|
SVCS+=("$sib")
|
|
echo " + $sib — 같은 이미지를 쓴다(옛 코드로 남지 않게 함께 간다)"
|
|
}
|
|
done
|
|
done
|
|
done
|
|
fi
|
|
echo "▶ 대상: ${SVCS[*]}"
|
|
fi
|
|
|
|
# ── 빌드 · 교체 ─────────────────────────────────────────────────────
|
|
# build 절이 없는 서비스(node:24-alpine · nginx:alpine)는 build 가 조용히 건너뛴다.
|
|
echo "▶ build"
|
|
docker compose build ${SVCS+"${SVCS[@]}"}
|
|
|
|
echo "▶ up -d"
|
|
docker compose up -d --force-recreate ${SVCS+"${SVCS[@]}"}
|
|
|
|
echo
|
|
docker compose ps
|
|
echo
|
|
echo "로그: ./log.sh"
|