Skip to content
Back to skills

Ubuntu1 Deploy

CSecurity

ubuntu1 배포/재배포/롤아웃, second-brain 이미지 빌드·compose up·마이그레이션 배포를 할 때 사용. rsync 경로·이미지 태그·실행 이미지 ID 대조·백업까지 체크리스트로 강제한다.

  • 15 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
devopspythongobashsqlnodedockergitapi

Works with

  • api
  • mcp

Security analysis

C63/100
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
  • criticalSends environment variables or credentials to an external URL
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro shows the line behind each finding and how to fix it

Scanned September 29, 2026

npx -y skills add baekenough/second-brain --skill ubuntu1-deploy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ubuntu1 Deploy?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ubuntu1 Deploy
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/baekenough-ubuntu1-deploy/badge)](https://www.skillsdirectory.com/skills/baekenough-ubuntu1-deploy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ubuntu1-deploy
description: "ubuntu1 배포/재배포/롤아웃, second-brain 이미지 빌드·compose up·마이그레이션 배포를 할 때 사용. rsync 경로·이미지 태그·실행 이미지 ID 대조·백업까지 체크리스트로 강제한다."
scope: package
user-invocable: true
---

## 언제 쓰나

- "ubuntu1에 배포해줘" / "재빌드해줘" / "롤아웃해줘" 같은 요청
- second-brain 이미지(server/collector/mcp/web) 빌드 후 ubuntu1 compose 스택 갱신
- 마이그레이션 파일 추가 후 배포 (자동 적용됨 — 아래 5번 참고)

**핵심 전제**: `ubuntu1:~/second-brain-app`는 git 리포가 아니라 로컬 리포의 rsync 사본이다. 로컬에서 커밋해도 ubuntu1에는 반영되지 않는다 — 반드시 rsync로 밀어야 한다. compose에는 `build:` 섹션이 없어 이미지 태그만 참조하므로, 로컬에서 빌드한 이미지를 올리는 게 아니라 **ubuntu1 서버 위에서 직접 빌드**한다.

## 사전점검 체크리스트 (착수 전)

- [ ] 이번 배포가 데이터 변형 마이그레이션을 포함하는가? → 포함하면 4번(백업) 필수
- [ ] `ssh ubuntu1 'bash -l -s'` 로그인 셸로 접속 가능한가 (로그인 셸 없으면 PATH/env 깨짐)
- [ ] 로컬 macOS rsync는 openrsync다 — `-az`(+웹은 `-R`)만 쓸 것, rsync 3.x 전용 옵션(`--info=stats2` 등)은 0바이트 실패 유발
- [ ] Go 변경과 웹 변경을 **같은 rsync 명령에 섞지 않는다** (경로 평탄화 사고 재발 방지, 사고 사례 2 참고)
- [ ] `ubuntu1:~/second-brain-app/docker-compose.ubuntu1.yml`은 리포의 `deploy/ubuntu1-stack/docker-compose.yml`과 **별도 관리** — compose 변경이 있으면 ubuntu1 쪽도 수동 갱신
- [ ] 빌드 태그가 compose가 참조하는 이름과 정확히 일치하는지 사전 확인 (`second-brain-<svc>:ubuntu1`)
- [ ] 개인데이터(SMS·통화·메일 본문, API 키) 출력 금지 — 검증은 집계·개수·해시로만

## 단계별 명령

### 1. Go 소스 전송

```bash
rsync -az --exclude '.git' cmd internal migrations go.mod go.sum Dockerfile FOR-AGENTS.md ubuntu1:~/second-brain-app/
```

### 2. 웹 소스 전송 (Go와 분리된 명령, 반드시 `-R`)

```bash
rsync -azR --exclude node_modules --exclude .next \
  web/src web/package.json web/bun.lock web/next.config.ts web/tsconfig.json web/Dockerfile \
  ubuntu1:~/second-brain-app/
```

전송 직후 메인 Dockerfile이 덮이지 않았는지 확인:

```bash
ssh ubuntu1 "grep -cE '^FROM .* AS (server|collector|mcp)' ~/second-brain-app/Dockerfile"
# 결과가 3이 아니면 즉시 중단 — web/Dockerfile이 메인 Dockerfile을 덮어썼을 가능성 (사고 사례 2)
```

### 3. 이미지 빌드 (ubuntu1 위에서 — compose에 build: 섹션 없음, 직접 빌드)

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
cd ~/second-brain-app
docker build --target server -t second-brain-server:ubuntu1 .
docker build --target collector -t second-brain-collector:ubuntu1 .
docker build --target mcp -t second-brain-mcp:ubuntu1 .
docker build -f web/Dockerfile -t second-brain-web:ubuntu1 web/
EOF
```

태그는 `deploy/ubuntu1-stack/docker-compose.yml`(및 ubuntu1의 `docker-compose.ubuntu1.yml`)이 참조하는 이름과 **한 글자도 다르면 안 된다**. 다른 태그로 빌드하면 `up -d`가 옛 이미지를 그대로 재시작하고도 정상처럼 보인다 (사고 사례 1).

### 4. (데이터 변형 마이그레이션이 있을 때만) 배포 전 백업

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
mkdir -p ~/backups
docker exec second-brain-postgres sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t documents -t chunks -Fc' \
  > ~/backups/pre-<마이그레이션번호>-$(date +%Y%m%d-%H%M).dump
EOF
```

사후 대조를 위해 배포 전 집계 SQL(예: `SELECT count(*), source_type FROM documents GROUP BY source_type`) 결과도 같이 남겨둔다.

### 5. 기동

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
cd ~/second-brain-app
docker compose --env-file .env.local -f docker-compose.ubuntu1.yml up -d <서비스...>
EOF
```

`--env-file`을 빠뜨리면 환경변수가 비어 들어간다. 마이그레이션은 server 기동 시 `RunMigrations`가 자동 실행한다 — **추적 테이블이 없어 매 부팅마다 전체 재실행**되므로 마이그레이션 SQL은 반드시 멱등해야 한다.

## 검증 체크리스트 (헬스체크로 대체 불가)

**1순위 — 이미지 ID 대조 (다른 모든 검증보다 먼저 실행)**

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
for svc in server collector mcp web; do
  running=$(docker inspect -f '{{.Image}}' second-brain-ubuntu1-${svc}-1)
  built=$(docker images -q second-brain-${svc}:ubuntu1)
  echo "${svc}: running=${running} built=${built}"
done
EOF
```

`running`과 `built`가 다르면 배포 실패다. `up -d` 출력에 `Recreated`가 없고 `Starting`/`Started`만 있었다면 이 신호다. 헬스체크·엔드포인트 200 응답은 옛 이미지도 통과하므로 증거로 인정하지 않는다.

- [ ] 이미지 ID 일치 (위 명령)
- [ ] `~/bin/verify-mounts.sh; echo exit=$?` → exit=0
- [ ] server 로그에 `migration applied` / `server listening`
- [ ] 워커·수집기 로그에 첫 tick 확인
- [ ] API 스모크 테스트 (응답 본문은 화면에 출력하지 말고 상태 코드·결과 건수만 확인)

`q` 없는 `/api/v1/search`는 `search.ValidateQueryInput`이 400으로 거부해 스모크로 쓸 수 없다(#285). 무해한 단어를 넣어 200과 결과 건수만 본다.

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
cd ~/second-brain-app
AUTH="Authorization: Bearer $(grep '^API_KEY=' .env.local | cut -d= -f2-)"
curl -s -H "$AUTH" \
  'http://127.0.0.1:8081/api/v1/search?q=%ED%9A%8C%EC%9D%98&limit=1' \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print("count:", d.get("count"))'
EOF
```

- [ ] (선택) v0.25.1 입력 검증 확인 — NUL 바이트가 400인지 (`search.ValidateQueryInput`, #282)

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
cd ~/second-brain-app
AUTH="Authorization: Bearer $(grep '^API_KEY=' .env.local | cut -d= -f2-)"
curl -s -o /dev/null -w '%{http_code}\n' -H "$AUTH" \
  'http://127.0.0.1:8081/api/v1/search?q=a%00b&limit=1'
EOF
```

기대값: `400`. 다른 값이면 v0.25.1 입력 검증이 배포 이미지에 없다는 뜻이다.

- [ ] (선택, **이미지 ID 대조가 MATCH일 때만 실행**) GraphQL 순환 fragment 400 확인 (#282)

> **경고**: 이 요청은 `graphql-go` 검증기의 무한 재귀 버그(순환 fragment에서 스택 오버플로 → 복구 불가 → 프로세스 종료)를 건드린다. v0.25.1 이전 이미지(AST 가드 미적용)에 보내면 **서버 프로세스가 죽는다**. 위 "1순위 — 이미지 ID 대조"가 MATCH임을 먼저 확인한 뒤에만 실행할 것.

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
cd ~/second-brain-app
AUTH="Authorization: Bearer $(grep '^API_KEY=' .env.local | cut -d= -f2-)"
curl -s -o /dev/null -w '%{http_code}\n' -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"query":"query { ...A } fragment A on Query { ...B } fragment B on Query { ...A }"}' \
  'http://127.0.0.1:8081/api/v1/graphql'
EOF
```

기대값: `400`(AST 가드가 실행 전에 거부). 요청이 멈추거나 응답이 없으면 즉시 컨테이너 상태(`docker ps`)를 확인할 것 — 프로세스가 죽었을 수 있다.

- [ ] (백업했다면) 배포 후 집계가 사전 집계와 기대한 방향으로만 달라졌는지 대조

## 사고 사례 (2026-09-19 야간, 같은 절차 5회 반복 중 발생)

### 사고 1 — 태그 오기로 옛 이미지 재시작을 "배포 완료"로 오보

`docker build -t second-brain-ubuntu1-server:latest .`처럼 compose가 참조하지 않는 이름으로 빌드 → `up -d`가 옛 이미지를 그대로 재시작 → 헬스체크·엔드포인트가 정상 응답해 배포 완료로 보고. 실제로는 골든셋 폴백·분류 큐 수정 2건이 미배포 상태였다.

**신호**: `up -d` 출력에 `Recreated` 없이 `Starting`/`Started`만 있음.
**방어**: 검증 1순위(이미지 ID 대조)를 항상 먼저 실행.

### 사고 2 — rsync 경로 평탄화로 메인 Dockerfile 덮어쓰기

`rsync -az cmd internal ... web/src web/Dockerfile ubuntu1:~/second-brain-app/`처럼 Go와 웹 파일을 한 명령에 섞어 상대경로(`-R`) 없이 전송 → 마지막 경로 요소만 남아 `web/Dockerfile`이 루트의 메인 Dockerfile을 덮어쓰고 `web/src`가 루트 `src/`로 흩어짐 → 빌드 시 `target stage "server" could not be found`.

**신호**: 빌드 에러 `target stage "X" could not be found`, 또는 루트에 낯선 `src/` 디렉토리 생성.
**방어**: Go/웹 rsync 명령을 분리하고 웹은 반드시 `-R`, 전송 후 `grep -cE '^FROM .* AS (server|collector|mcp)' Dockerfile`이 3인지 확인.

## 롤백

**이미지 문제 (사고 1 유형)**: 직전 정상 배포 시점의 이미지가 로컬(ubuntu1)에 남아있다면 재태깅 후 재기동.

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
docker tag second-brain-server:ubuntu1 second-brain-server:ubuntu1-broken   # 실패한 이미지 보관
docker tag <이전_정상_이미지_ID> second-brain-server:ubuntu1
cd ~/second-brain-app && docker compose --env-file .env.local -f docker-compose.ubuntu1.yml up -d server
EOF
```

이전 이미지가 이미 삭제됐다면 로컬 리포에서 배포 직전 커밋으로 되돌려 1~3단계를 다시 수행한다.

**데이터 문제 (마이그레이션 부작용)**: 4단계에서 만든 덤프로 복원. 복원 전 사전 집계와 현재 상태를 비교해 실제로 롤백이 필요한 사고인지(예상된 변화가 아닌지) 먼저 확인한다.

```bash
ssh ubuntu1 'bash -l -s' <<'EOF'
docker compose --env-file .env.local -f docker-compose.ubuntu1.yml stop server collector mcp
docker exec -i second-brain-postgres pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists < ~/backups/pre-<번호>-<타임스탬프>.dump
docker compose --env-file .env.local -f docker-compose.ubuntu1.yml up -d
EOF
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…