chore: containerize for Coolify + docs (stage 5)
- Dockerfile (python:3.13-slim, uvicorn, 비루트, PORT, /healthz HEALTHCHECK) - .dockerignore - README: 로컬/컨테이너/Coolify 배포 + 폐쇄망 wheelhouse 반입 절차 - CLAUDE.md 갱신(현재 구현 상태/라우트/명령/배포), MANUAL.md 추가 - 정적자산은 이미 로컬 동봉(런타임 CDN 0) → 최종 폐쇄망 반입은 의존성 wheelhouse만 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
.dockerignore
Normal file
17
.dockerignore
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
**/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
tests/
|
||||||
|
ref/
|
||||||
|
*.md
|
||||||
|
.env
|
||||||
|
.env.example
|
||||||
|
.claude/
|
||||||
|
vendor/
|
||||||
|
audit-log.jsonl
|
||||||
99
CLAUDE.md
99
CLAUDE.md
@@ -12,17 +12,23 @@ Context: it runs in an **air-gapped (closed) network** at the Bank of Korea IT
|
|||||||
Strategy Dept. cloud team. The console lets operators mirror, snapshot, verify,
|
Strategy Dept. cloud team. The console lets operators mirror, snapshot, verify,
|
||||||
and deploy RPM/DEB patch repositories by clicking instead of running CLI commands.
|
and deploy RPM/DEB patch repositories by clicking instead of running CLI commands.
|
||||||
|
|
||||||
The full implementation spec is **`pulp-console-spec.md`** (written in Korean,
|
The full implementation spec is **`pulp-console-spec.md`** (Korean). `PLAN.md`
|
||||||
addressed to the implementing AI). It is the source of truth — read it before
|
tracks staged progress (checkboxes); `ref/design-ref.md` is the visual design
|
||||||
making design decisions. The codebase itself does not yet exist; the spec
|
standard; `MANUAL.md` is the Coder/Gitea/Coolify dev+deploy environment guide.
|
||||||
describes what to build and in what order.
|
|
||||||
|
Status: stages 0–4 are implemented and tested (dashboard, sync+polling, version
|
||||||
|
list, gated deploy+audit). Stage 5 = containerization/packaging. Code lives under
|
||||||
|
`app/` (`config.py`, `pulp_client.py`, `deps.py`, `views.py`, `audit.py`,
|
||||||
|
`task_store.py`, `routes/`, `templates/`, `static/`); tests under `tests/`.
|
||||||
|
|
||||||
## Stack (decided — do not substitute)
|
## Stack (decided — do not substitute)
|
||||||
|
|
||||||
- **Backend / BFF:** FastAPI (Python), using `httpx` to call Pulp.
|
- **Backend / BFF:** FastAPI (Python), using `httpx` to call Pulp.
|
||||||
- **Frontend:** HTMX + Jinja2 templates. **No build step.** Buttons trigger
|
- **Frontend:** HTMX + Jinja2 templates. **No build step.** Buttons trigger
|
||||||
partial-HTML swaps; progress polling is done with `hx-trigger="every 3s"`.
|
partial-HTML swaps; progress polling is done with `hx-trigger="every 3s"`.
|
||||||
- **Styling:** Pico.css (preferred for air-gap simplicity) or Tailwind standalone.
|
- **Styling:** Pico.css + a custom layer (`app/static/app.css`) applying the
|
||||||
|
`ref/design-ref.md` tokens (Toss-style). Pretendard font is vendored locally.
|
||||||
|
No decorative emoji in chrome — status uses colored chips/dots.
|
||||||
|
|
||||||
## Hard constraints (these are the point of the project)
|
## Hard constraints (these are the point of the project)
|
||||||
|
|
||||||
@@ -66,36 +72,50 @@ name, in code.** Sync and deploy return async **tasks** — capture `task_href`
|
|||||||
poll `GET {task_href}` (`state`: waiting/running/completed/failed) for completion.
|
poll `GET {task_href}` (`state`: waiting/running/completed/failed) for completion.
|
||||||
RPM paths use `rpm/rpm`; the DEB equivalent swaps that segment for `deb/apt`.
|
RPM paths use `rpm/rpm`; the DEB equivalent swaps that segment for `deb/apt`.
|
||||||
|
|
||||||
## Safety model (deploy is gated)
|
## Safety model (deploy is gated) — implemented in `routes/deploy.py`
|
||||||
|
|
||||||
- Deploy must go through a **confirmation modal** (`hx-confirm` or an explicit step).
|
- **Verification gate** (re-checked server-side): only `repo_config` GPG-passing
|
||||||
- The deploy button is **disabled for versions that aren't verified** (GPG /
|
repos are deployable; the button is disabled otherwise. Note verification is
|
||||||
checksum passed). Verification comes from the Remote's `gpgkey`/`tls_validation`
|
**repo-level config**, so it indicates "verification is configured", not a
|
||||||
and the Repository `repo_config` (`gpgcheck: 1`, `repo-gpgcheck: 1`) — this is
|
per-snapshot cryptographic proof (real package checks happen at sync time).
|
||||||
the basis for the "verified ✅" badge.
|
- **Confirmation modal** with a **preview** (current → target version, net package
|
||||||
- Every deploy is written to an **audit log** (who / when / which repo / which version).
|
delta) and **type-to-confirm** (operator must type the repo name).
|
||||||
|
- **Audit log → Postgres** (`audit.py`, table `deploy_audit`): who / when / repo /
|
||||||
|
from-version → to-version (rollback trail). DB write failure does not 500 a
|
||||||
|
completed deploy — it surfaces an `audit_ok=False` warning (TODO: hard-fail if
|
||||||
|
audit becomes mandatory policy).
|
||||||
|
- **Two-step clarity**: deploy = create publication → PATCH distribution; on
|
||||||
|
failure the UI states clearly whether production was changed.
|
||||||
|
- **Auth is a TODO** (`operator` is a placeholder; deploy-time password re-entry planned).
|
||||||
|
|
||||||
## Planned route surface (BFF)
|
## Route surface (BFF)
|
||||||
|
|
||||||
```
|
```
|
||||||
GET / dashboard (full page)
|
GET / dashboard (full page)
|
||||||
GET /repos repo list fragment
|
GET /repos repo list fragment
|
||||||
POST /repos/{uuid}/sync start sync, return progress-bar fragment
|
POST /repos/{uuid}/sync start sync, return progress-bar fragment
|
||||||
GET /repos/{uuid}/progress progress-bar fragment (polled every 3s)
|
GET /repos/{uuid}/progress progress-bar fragment (polled every 3s)
|
||||||
GET /repos/{uuid}/versions version list fragment/page
|
GET /repos/{uuid}/versions version list page
|
||||||
POST /repos/{uuid}/deploy confirm deploy (publication + distribution), result fragment
|
GET /repos/{uuid}/deploy/confirm deploy confirmation modal (preview + type-to-confirm)
|
||||||
GET /healthz proxy Pulp status
|
POST /repos/{uuid}/deploy gated deploy (publication + distribution) + audit
|
||||||
|
GET /healthz app liveness — always {"ok": true} (container health)
|
||||||
|
GET /pulp-status Pulp connectivity badge (dashboard polls this)
|
||||||
```
|
```
|
||||||
|
|
||||||
Pulp access is centralized in a `pulp_client.py` helper (`get`/`post`/`patch`
|
Pulp access is centralized in `pulp_client.py` (`get`/`post`/`patch` with Basic
|
||||||
with Basic Auth, `list_repos`, `sync_repo`, `list_versions`,
|
Auth, plus `status`, `list_repos`, `get_repo`, `get_version`, `list_versions`,
|
||||||
`create_publication`, `update_distribution`, single task fetch — the *page* does
|
`sync_repo`, `get_task`, `list_distributions`, `get_publication`,
|
||||||
the polling, not the client).
|
`create_publication`, `update_distribution`, `wait_for_task`). HTML polling is the
|
||||||
|
*page's* job; the client only single-fetches — except `wait_for_task`, used only
|
||||||
|
for the compound deploy (publication→distribution) where server-side waiting is
|
||||||
|
needed. In-flight syncs are tracked in `task_store.py` (in-memory; single
|
||||||
|
instance only). View-model shaping (pure, httpx-free) lives in `views.py`.
|
||||||
|
|
||||||
## Configuration (env vars)
|
## Configuration (env vars)
|
||||||
|
|
||||||
`PULP_BASE_URL`, `PULP_USERNAME`, `PULP_PASSWORD` (secret), `PULP_VERIFY_TLS`,
|
`PULP_BASE_URL`, `PULP_USERNAME`, `PULP_PASSWORD` (secret), `PULP_VERIFY_TLS`,
|
||||||
`PULP_CA_FILE` (path to internal CA pem).
|
`PULP_CA_FILE` (internal CA pem), `DATABASE_URL` (Postgres for audit log),
|
||||||
|
`PORT` (default 8000), `PULP_DEMO` (fake data for screen preview, default off).
|
||||||
|
|
||||||
## Build order (from the spec — build in working-screen increments)
|
## Build order (from the spec — build in working-screen increments)
|
||||||
|
|
||||||
@@ -108,8 +128,31 @@ the polling, not the client).
|
|||||||
|
|
||||||
Each step should produce a working screen. Do not build everything at once.
|
Each step should produce a working screen. Do not build everything at once.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/Scripts/python.exe -m uvicorn app.main:app --reload # run (http://127.0.0.1:8000)
|
||||||
|
.venv/Scripts/python.exe -m pytest -q # tests
|
||||||
|
.venv/Scripts/python.exe -m ruff check app tests # lint
|
||||||
|
.venv/Scripts/python.exe -m ruff format app tests # format
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests mock the Pulp client via FastAPI `dependency_overrides` (route tests) or
|
||||||
|
`respx` (client tests); `views.py` is unit-tested as pure functions. Audit DB
|
||||||
|
writes are intercepted by monkeypatching `app.audit._write`.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Now (demo): **Coolify** builds the repo `Dockerfile` (Build Pack: Dockerfile,
|
||||||
|
Port 8000) and injects env vars — see `README.md` / `MANUAL.md`. `/healthz` is
|
||||||
|
liveness only so Pulp being down doesn't fail the container.
|
||||||
|
|
||||||
|
Final target is the **air-gapped** network: static assets are already vendored
|
||||||
|
(no runtime CDN); only Python deps need offline reintroduction via a wheelhouse
|
||||||
|
(`pip download` → swap the Dockerfile install step). Procedure in `README.md`.
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- Python 3.14 is installed locally. Once code exists, prefer a virtualenv and
|
- Python 3.14 local venv; deps pinned in `requirements.txt` (container uses
|
||||||
pin dependencies (`requirements.txt` / lockfile) so they can be carried into
|
`python:3.13-slim`). Reference Pulp objects by `pulp_href`, not name.
|
||||||
the air-gapped network via pip.
|
- Each stage should produce a working screen and stay green (tests + ruff) before commit.
|
||||||
|
|||||||
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Pulp 패치 관리 콘솔 — Coolify(Dockerfile build pack)용 이미지.
|
||||||
|
# 빌드 검증: 워크스페이스에서 `podman build -t pulp-console .` (MANUAL §10)
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PORT=8000
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 1) 의존성 (레이어 캐시). 폐쇄망 반입 시 이 단계를 wheelhouse 오프라인 설치로 교체 — README 참고.
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 2) 앱 소스 (정적자산·템플릿 포함 — 런타임 CDN 의존 0)
|
||||||
|
COPY app ./app
|
||||||
|
|
||||||
|
# 3) 비루트 실행
|
||||||
|
RUN useradd --create-home --uid 10001 appuser
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# 라이브니스: /healthz 는 Pulp 와 무관하게 200 (Coolify health check 용)
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
CMD python -c "import os,urllib.request; urllib.request.urlopen('http://127.0.0.1:%s/healthz' % os.environ.get('PORT','8000'))" || exit 1
|
||||||
|
|
||||||
|
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}"]
|
||||||
486
MANUAL.md
Normal file
486
MANUAL.md
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
# 개발환경 사용 매뉴얼 (Coder)
|
||||||
|
|
||||||
|
사내 개발은 **Coder**(웹 기반 개발 워크스페이스)에서 합니다.
|
||||||
|
브라우저만 있으면 됩니다.
|
||||||
|
이 개발환경에는 DB, MinIO, Coding Agent, git, 배포환경이 미리 연결돼 있어, 로그인 후 바로 이용할 수 있습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 준비물
|
||||||
|
- 인터넷 접속 가능한 브라우저 (Chrome/Edge 권장)
|
||||||
|
|
||||||
|
### 주소(URL) 한눈에
|
||||||
|
|
||||||
|
| 용도 | 주소 | 쓰는 곳 | 계정(ID/PW) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **개발 워크스페이스** | https://coder.bokdev.in | 로그인·VS Code (1~3번) | 웹메일 주소 / `bok1234!!행번` |
|
||||||
|
| **코드 저장소(Git)** | https://gitea.bokdev.in | repo 생성·push (11번) | 웹메일 주소 / `bok1234!!행번` |
|
||||||
|
| **배포(Coolify)** | https://coolify.bokdev.in | 앱 빌드·배포·로그 (12번) | 웹메일 주소 / `bok1234!!행번` |
|
||||||
|
| **파일저장소(MinIO 콘솔)** | https://minioc.bokdev.in | 업로드된 파일 눈으로 확인 (선택) |
|
||||||
|
| **개발 중 앱 미리보기** | `https://<자동생성>.coder.bokdev.in` | 로컬 실행 앱 브라우저 확인 (9·10번, VS Code PORTS가 자동 발급) |
|
||||||
|
| **배포된 앱 주소** | `https://<이름>.apps.bokdev.in` | 실제 서비스 확인 (12번, Coolify에서 도메인 지정) |
|
||||||
|
|
||||||
|
> 코드에서 쓰는 주소(자동 주입, 직접 입력 불필요): **DB** `10.200.0.152:5432` · **MinIO API** `https://minio.bokdev.in` · **AI 게이트웨이** `https://litellm.bok.or.kr`.
|
||||||
|
> (콘솔 `minioc` 와 API `minio` 는 다릅니다 — 코드는 `minio`, 사람이 눈으로 볼 땐 `minioc`.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Coder 로그인
|
||||||
|
|
||||||
|
1. 브라우저에서 **https://coder.bokdev.in** 접속
|
||||||
|
2. 로그인
|
||||||
|
- **Username**: 본인 행번 (예: `2620227`)
|
||||||
|
- **Password**: `bok1234!!` + 본인 사번 (예: `bok1234!!2620227`)
|
||||||
|
→ 최초 로그인 후 비밀번호를 바꾸시기 바랍니다. (우측 상단 계정 메뉴 → `Account` → `Security`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 워크스페이스 만들기 (최초 1회)
|
||||||
|
|
||||||
|
워크스페이스 = 본인 전용 개발용 컨테이너(VS Code + 각종 도구가 깔린 PC).
|
||||||
|
|
||||||
|
1. 로그인하면 **Workspaces** 화면. **`Create Workspace`** 클릭 (또는 **Templates → `aidev`** 선택)
|
||||||
|
2. 설정값 입력
|
||||||
|
- **Name**: 워크스페이스 이름 (예: `ws-aidev-<사번>` 또는 자유롭게)
|
||||||
|
- **External Authentication** : Gitea(그대로 둠)
|
||||||
|
- **CPU / Memory / Home disk size**: 기본값(2 Core / 4 GiB / 10 GiB)으로 두면 됩니다. 필요하면 나중에 늘릴 수 있습니다.
|
||||||
|
|
||||||
|
* (Optional) Gitea 계정 연동
|
||||||
|
* git 원격 계정을 사전 연동할 수 있습니다. 지금 연동하지 않아도 추후 workspace 터미널에서 연동 가능합니다.
|
||||||
|
* `External Authentication` - `애플리케이션 승인`
|
||||||
|

|
||||||
|
|
||||||
|
3. **`Create Workspace`** 클릭
|
||||||
|
4. 워크스페이스가 빌드됩니다 (**처음엔 이미지 다운로드로 2~5분** 걸릴 수 있습니다. VS Code Web 까지 아이콘이 표시될 때까지 기다리세요).
|
||||||
|
- 상태가 **Running** 이 되면 준비 완료.
|
||||||
|
|
||||||
|
> 이 워크스페이스는 한번 만들면 계속 사용합니다. 한번 만들어진 워크스페이스는 가상 PC처럼 **Start** 만 누르면 됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. VS Code 열기
|
||||||
|
|
||||||
|
1. 워크스페이스 화면에서 **`VS Code Web`** 버튼(두번째 아이콘) 클릭
|
||||||
|
2. 브라우저에 VS Code가 열리고, 자동으로 **`/home/coder/projects`** 폴더가 열립니다. (Yes. I trust the authors 클릭)
|
||||||
|
3. 그 안에 **`sample`** 폴더가 이미 있습니다 — 참조용 예제입니다(직접 고치지 말고 복사해 쓰세요, 7번 참고).
|
||||||
|
|
||||||
|
> 작업 파일은 반드시 **`/home/coder/projects`** 아래에 두세요. 이 폴더만 영구 보존됩니다.
|
||||||
|
> (워크스페이스를 stop/재시작해도 유지. 그 밖의 위치는 사라질 수 있습니다.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 미리 연결된 것들 (별도 설정 불필요)
|
||||||
|
|
||||||
|
새 워크스페이스에는 아래가 자동으로 준비돼 있습니다.
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| **개발 도구** | Java(JDK)/Maven, Node 22, Python 3.12, git, psql, `tree`, `net-tools`(netstat/ifconfig) |
|
||||||
|
| **컨테이너** | `podman` (그리고 `docker` 명령도 동일하게 동작 — podman 별칭) |
|
||||||
|
| **DB** | 본인 전용 Postgres 스키마에 자동 연결 (`$DATABASE_URL`) |
|
||||||
|
| **VS Code 확장** | Claude Code, Codex (이미 설치됨) |
|
||||||
|
| **AI CLI** | `claude`, `codex`, `gemini` (LiteLLM 게이트웨이 연동, 아래 5번 참고) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. AI 에이전트 사용 설정 (최초 1회) — LiteLLM 키 입력
|
||||||
|
|
||||||
|
Claude Code / Codex / Gemini 가 사내 AI 게이트웨이를 쓰려면 **본인 LiteLLM virtual key**가 필요합니다.
|
||||||
|
키는 **한 곳에만** 넣으면 모든 도구(CLI·확장)가 공유합니다.
|
||||||
|
|
||||||
|
1. 터미널 열기: VS Code 좌상단 맨위 메뉴 버튼(작대기 3개) **Terminal → New Terminal**
|
||||||
|
2. 화면 아래 터미널 창이 나오면, 다음 명령을 입력하고, 안내가 나오면 발급받은 본인 키(`sk-...`)를 붙여넣습니다:
|
||||||
|
```bash
|
||||||
|
update-litellm-key
|
||||||
|
```
|
||||||
|
```
|
||||||
|
LiteLLM virtual key 입력 (sk-...): sk-여기에-본인-키-붙여넣기
|
||||||
|
키 갱신 완료 (len=25). 현재 터미널에 즉시 적용됨.
|
||||||
|
```
|
||||||
|
> `update-litellm-key` 는 키를 파일에 저장하고 **현재 터미널에 바로 적용**합니다.
|
||||||
|
> 터미널을 새로 열거나 `source` 할 필요가 없습니다. 키를 바꿀 때도 같은 명령을 다시 쓰면 됩니다.
|
||||||
|
3. 확인:
|
||||||
|
```bash
|
||||||
|
echo $ANTHROPIC_BASE_URL # https://litellm.bok.or.kr 가 나오면 정상
|
||||||
|
claude # Claude Code CLI 실행
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
echo $GOOGLE_GEMINI_BASE_URL # https://litellm.bok.or.kr 가 나오면 정상
|
||||||
|
gemini # Gemini CLI 실행
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
echo $OPENAI_BASE_URL # https://litellm.bok.or.kr/v1 가 나오면 정상
|
||||||
|
codex # Codex CLI 실행
|
||||||
|
```
|
||||||
|
|
||||||
|
각 도구의 **기본 모델**은 사내 게이트웨이에 맞춰 미리 설정돼 있습니다(바꾸려면 각 설정 파일 수정):
|
||||||
|
|
||||||
|
| 도구 | 기본 모델 | 설정 파일 |
|
||||||
|
|---|---|---|
|
||||||
|
| Claude Code | `claude-opus-4-8` | `~/.claude/settings.json` |
|
||||||
|
| Codex | `gpt-5.5` | `~/.codex/config.toml` |
|
||||||
|
| Gemini | `gemini-3.1-pro-preview` | `~/.gemini/settings.json` |
|
||||||
|
|
||||||
|
> 키는 본인 워크스페이스 안에만 저장되고, 코드/깃에는 올라가지 않습니다.
|
||||||
|
> 게이트웨이 주소(`https://litellm.bok.or.kr`)는 이미 설정돼 있으니 건드릴 필요 없습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Git(Gitea) 사용 — 최초 1회 승인
|
||||||
|
|
||||||
|
코드 저장소는 사내 **Gitea**(https://gitea.bokdev.in)입니다. 토큰 입력 없이 자동 인증됩니다.
|
||||||
|
|
||||||
|
1. 터미널에서 처음 `git clone` / `git push` 등을 하면, **Gitea 승인 화면**으로 안내됩니다.
|
||||||
|
- 또는 Coder 화면의 **`Gitea`** external auth 항목에서 **Authorize** 를 미리 눌러도 됩니다.
|
||||||
|
2. 한 번 **Authorize(승인)** 하면, 이후로는 비밀번호 입력 없이 clone/push 가 됩니다.
|
||||||
|
|
||||||
|
예:
|
||||||
|
```bash
|
||||||
|
cd ~/projects
|
||||||
|
|
||||||
|
## 만약 새로운 repo를 다운로드 하고 싶을 경우
|
||||||
|
git clone https://gitea.bokdev.in/<org>/<repo>.git
|
||||||
|
|
||||||
|
## 사용예
|
||||||
|
git clone https://gitea.bokdev.in/playground/sample.git
|
||||||
|
|
||||||
|
## 새로운 버전으로 동기화
|
||||||
|
cd ~/projects/sample
|
||||||
|
git add .
|
||||||
|
git pull
|
||||||
|
# gitea 행번 / 비밀번호
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## 7. 새 프로젝트 시작하기 (sample 복사)
|
||||||
|
|
||||||
|
`~/projects/sample` 은 바로 돌려볼 수 있는 Node 예제이며 **읽기 전용 참조**입니다.
|
||||||
|
직접 고치지 말고, **`new-project` 명령으로 복사**해서 본인 프로젝트를 시작하세요.
|
||||||
|
|
||||||
|
**(1) 터미널 열기** — VS Code 상단 메뉴 **Terminal → New Terminal** (화면 아래쪽에 터미널 창이 뜹니다)
|
||||||
|
|
||||||
|
**(2) 프로젝트 만들기** — 터미널에 입력:
|
||||||
|
```bash
|
||||||
|
new-project myapp # sample 을 ~/projects/myapp 으로 복사 + .project-env(DB/S3) 자동생성
|
||||||
|
```
|
||||||
|
> `myapp` 은 예시입니다. 원하는 이름으로 바꿔도 됩니다.
|
||||||
|
|
||||||
|
**(3) VS Code 로 그 폴더 열기** — 만든 폴더를 편집기에 띄웁니다(왼쪽 파일 목록에 보이게):
|
||||||
|
- 상단 메뉴 **File → Open Folder…**
|
||||||
|
- 경로 입력칸에 **`/home/coder/projects/myapp`** 입력 → **OK**
|
||||||
|
- (또는 왼쪽 맨 위 📁 **Explorer** 아이콘 → **Open Folder** 버튼)
|
||||||
|
- 창이 새로고침되며 왼쪽에 `myapp` 의 파일들이 보이면 성공입니다.
|
||||||
|
|
||||||
|
> 폴더를 열면 VS Code 가 그 폴더를 "작업 공간"으로 삼습니다. 이제 왼쪽 목록에서 파일을 눌러 편집하고,
|
||||||
|
> 터미널도 자동으로 그 폴더(`~/projects/myapp`)에서 시작됩니다.
|
||||||
|
|
||||||
|
**(4) 라이브러리 설치** — 다시 터미널(Terminal → New Terminal)에서:
|
||||||
|
```bash
|
||||||
|
npm install # 처음 1회: 필요한 라이브러리 설치 (이걸 안 하면 실행이 실패합니다)
|
||||||
|
```
|
||||||
|
|
||||||
|
여기까지 하면 프로젝트 준비 완료입니다. **이제 8번부터 본격적으로 개발**하면 됩니다(코드 편집 → AI 활용 → 실행 → 배포).
|
||||||
|
|
||||||
|
> **`.project-env` 가 그 프로젝트의 설정 파일입니다** (DB·S3). 값을 바꾸려면 이 파일 한 곳만 고치면 됩니다.
|
||||||
|
> 프로젝트 폴더에 들어가면(cd) 자동으로 환경변수에 반영됩니다. (git에는 올라가지 않습니다)
|
||||||
|
> LiteLLM 키만은 프로젝트가 아니라 워크스페이스 전체 공용이라 `~/.env`(5번 `update-litellm-key`)에서 관리합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 전체 개발 흐름 (한눈에)
|
||||||
|
|
||||||
|
```
|
||||||
|
7. 새 프로젝트 만들기 (new-project → 폴더 열기 → 설치) ← 위에서 완료
|
||||||
|
8. 개발하기 (코드 편집 + AI + DB/S3 사용)
|
||||||
|
9. 로컬에서 실행·확인 (npm run dev → 브라우저 미리보기)
|
||||||
|
10. 컨테이너로 빌드·확인 (podman build/run — 배포와 동일한 방식으로 검증)
|
||||||
|
11. Gitea에 올리기 (git push)
|
||||||
|
12. Coolify로 배포·확인 (실제 서비스로 띄우기)
|
||||||
|
```
|
||||||
|
아래는 각 단계를 순서대로 설명합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 개발하기
|
||||||
|
|
||||||
|
(7번에서 **File → Open Folder 로 본인 프로젝트 폴더를 열어 둔 상태**에서)
|
||||||
|
|
||||||
|
- **코드 편집**: 왼쪽 파일 목록에서 파일(예: `src/server.js`)을 눌러 수정 → **Ctrl+S 로 저장**.
|
||||||
|
- **AI 도구 활용**: 터미널에서 `claude`(또는 `codex`/`gemini`) 실행. **반드시 본인 프로젝트 폴더 안에서** 실행해야 그 프로젝트를 봅니다(폴더를 Open Folder 해뒀으면 새 터미널은 이미 그 폴더에서 시작). 키 설정은 5번.
|
||||||
|
- **내 DB 직접 접속**(필요 시):
|
||||||
|
```bash
|
||||||
|
cd ~/projects/<본인 프로젝트> # 프로젝트 폴더에서 (그래야 .project-env 가 로드됨)
|
||||||
|
psql "$DATABASE_URL" # 본인 전용 스키마로 바로 접속됨
|
||||||
|
```
|
||||||
|
- **참고**: DB(`$DATABASE_URL`)·S3 접속정보는 `.project-env` 에 이미 들어 있고, 프로젝트 폴더에 들어가면 자동 적용됩니다. 코드에서는 그냥 `process.env.DATABASE_URL` 등으로 쓰면 됩니다.
|
||||||
|
|
||||||
|
### 8-1. AI 도구를 잘 쓰는 법 (팁)
|
||||||
|
|
||||||
|
- **항상 프로젝트 폴더 안에서 실행**하세요. AI 는 "지금 폴더"의 파일을 읽어 맥락을 잡습니다.
|
||||||
|
- **`CLAUDE.md` 를 활용**하세요. sample 에는 이미 `CLAUDE.md`(이 프로젝트 규칙: podman 개발, Coolify 배포, 비밀값 금지 등)가 들어 있고, Claude 가 자동으로 읽습니다. 프로젝트 규칙·주의사항을 여기에 적어두면 AI 가 그대로 따릅니다.
|
||||||
|
- **구체적으로 시키세요**. "로그인 API 만들어줘" 보다 "`src/` 에 POST /login 엔드포인트 추가, 입력 검증하고 실패 시 401 반환" 처럼.
|
||||||
|
- **확인은 직접**: AI 가 만든 코드도 9번(로컬 실행)·10번(컨테이너)으로 **반드시 본인이 동작 확인** 후 커밋하세요.
|
||||||
|
- 키가 안 먹으면(401) → 5번 `update-litellm-key`. 모델 오류(400)면 → 5번 표의 모델명.
|
||||||
|
|
||||||
|
### 8-2. bkit — AI 개발 보조 플러그인 (선택, 권장)
|
||||||
|
|
||||||
|
**bkit**(Vibecoding Kit, https://www.bkit.ai/ )은 Claude Code 에 **체계적 개발 절차(PDCA: 계획→설계→구현→검증)** 와 다수의 전문 스킬을 더해주는 플러그인입니다. "무엇을 만들지"만 설명하면 bkit 이 계획부터 구현·검증까지 단계적으로 진행해 줍니다.
|
||||||
|
|
||||||
|
**설치 (최초 1회, Claude Code 안에서 입력)**
|
||||||
|
```
|
||||||
|
claude
|
||||||
|
|
||||||
|
# claude cli 프롬프트에서
|
||||||
|
/plugin marketplace add popup-studio-ai/bkit-claude-code
|
||||||
|
/plugin install bkit
|
||||||
|
```
|
||||||
|
|
||||||
|
**vscode extension (web)에서는 plugin 을 이용할 수 없습니다. plugin 을 이용하려면 cli 를 이용하십시오.**
|
||||||
|
|
||||||
|
**자주 쓰는 명령** (Claude Code 프롬프트에 입력)
|
||||||
|
- `/pdca pm <기능이름>` — 기능 하나를 계획→구현→검증까지 한 번에. **처음엔 이거 하나면 충분**합니다.
|
||||||
|
하지만 보통은
|
||||||
|
- `/pdca plan `
|
||||||
|
- `/pdca design`
|
||||||
|
- `/pdca do`
|
||||||
|
- `/pdca analyze`
|
||||||
|
- `/pdca iterate`
|
||||||
|
- `/sprint` — 여러 기능을 묶은 릴리스 단위 작업.
|
||||||
|
- `/control` — AI 가 얼마나 자동으로 진행할지(자율도) 조절.
|
||||||
|
|
||||||
|
> bkit 의 세부 절차를 몰라도 됩니다 — **원하는 걸 자연어로 말하면** bkit 이 알맞은 흐름을 골라 줍니다.
|
||||||
|
> 더 알아보려면 공식 사이트(bkit.ai) / GitHub(`popup-studio-ai/bkit-claude-code`) 참고.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 로컬에서 실행하고 브라우저로 확인하기
|
||||||
|
|
||||||
|
코드를 바로 실행해 동작을 확인하는 단계입니다(가장 빠름).
|
||||||
|
|
||||||
|
> **빌드·테스트는 4단계로 점점 "실제 배포에 가깝게" 검증합니다.**
|
||||||
|
> | 단계 | 무엇으로 | 무엇을 확인 | 어디서 |
|
||||||
|
> |---|---|---|---|
|
||||||
|
> | **9. 소스 실행** | `npm run dev` | 코드 로직 (가장 빠름) | 워크스페이스 |
|
||||||
|
> | **10. 컨테이너 빌드** | `podman build`+`run` | Dockerfile 이 제대로 빌드/기동되는지 | 워크스페이스 |
|
||||||
|
> | **11. Git push** | `git push` | 배포에 쓸 코드를 저장소에 올림 | Gitea |
|
||||||
|
> | **12. 배포 빌드** | Coolify | 실제 서비스로 빌드/기동 | Coolify |
|
||||||
|
> 앞 단계가 통과해야 뒤 단계가 거의 그대로 됩니다(같은 코드·같은 Dockerfile). 막히면 **앞 단계로 돌아가** 고치세요.
|
||||||
|
|
||||||
|
**1) 실행**
|
||||||
|
```bash
|
||||||
|
cd ~/projects/<본인 프로젝트>
|
||||||
|
npm run dev # 코드를 고치면 자동으로 다시 시작됨(핫리로드)
|
||||||
|
```
|
||||||
|
실행되면 터미널에 `sample listening on :3000` 같은 줄이 뜹니다. **에러 없이** 이 줄이 보이면 기동 성공입니다.
|
||||||
|
(빨간 에러가 뜨면 거의 ① `npm install` 안 함 ② 코드 문법 오류 ③ `.project-env` 미로딩(=폴더 밖에서 실행) 셋 중 하나입니다.)
|
||||||
|
|
||||||
|
**2) 연결만 빠르게 점검**(앱 안 띄우고 DB/파일저장소 자격증명·연결 확인):
|
||||||
|
```bash
|
||||||
|
npm run db:check # → "DB OK: { now: 2026-... }" 이면 DB 연결 정상
|
||||||
|
npm run minio:check # → "S3 OK: { bucket: 'coolify-user-data', sampleKeys: [...] }" 이면 정상
|
||||||
|
```
|
||||||
|
> `DB FAIL` / `S3 FAIL` 이 나오면 `.project-env` 값(host·키)을 확인하세요. **여기서 통과하면 배포 환경에서도 거의 됩니다.**
|
||||||
|
|
||||||
|
**3) 브라우저로 미리보기** — 워크스페이스는 사내 클러스터 안이라 `localhost:3000` 이 PC 브라우저엔 바로 안 열립니다. VS Code 의 포트 기능을 씁니다:
|
||||||
|
1. VS Code 하단 **`PORTS`** 탭 클릭 → **`Forward a Port`** → 포트 번호(`3000`) 입력
|
||||||
|
(앱이 뜨면 자동 감지해 알림이 뜨기도 합니다)
|
||||||
|
2. 포워딩된 포트 옆 **🌐 (Open in Browser)** 클릭 → 새 탭에 앱이 열립니다.
|
||||||
|
- 열리는 주소: `https://<자동생성>.coder.bokdev.in` (본인 전용 임시 URL, 사내 정식 TLS)
|
||||||
|

|
||||||
|
|
||||||
|
**4) 엔드포인트로 동작 확인** — 브라우저 주소 뒤에 경로를 붙이거나, 터미널에서 `curl` 로 확인합니다. **각 응답의 `"ok": true` 와 아래 기대값을 확인**하세요: (서비스 실행 상태에서)
|
||||||
|
|
||||||
|
| 경로 | 의미 | 정상 응답(예) |
|
||||||
|
|---|---|---|
|
||||||
|
| `/healthz` | 앱이 살아있나 | `{"ok":true}` |
|
||||||
|
| `/db` | 내 Postgres 연결 | `{"ok":true,"now":"2026-..."}` |
|
||||||
|
| `/s3` | 파일저장소(MinIO) 연결 | `{"ok":true,"bucket":"coolify-user-data","sampleKeys":[...]}` |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl 127.0.0.1:3000/healthz # {"ok":true}
|
||||||
|
curl 127.0.0.1:3000/db # DB 연결 + 현재시각
|
||||||
|
curl 127.0.0.1:3000/s3 # 버킷명 + 파일목록 일부
|
||||||
|
```
|
||||||
|
> `"ok": false` + `error` 가 보이면 그 메시지가 원인입니다(예: DB 비번 틀림, 버킷 없음). `/db 500` 은 보통 `.project-env` 의 `DATABASE_URL` 문제입니다.
|
||||||
|
> 이 임시 URL 은 본인만 접근 가능하고 워크스페이스를 끄면 사라집니다. **미리보기용**이며, 정식 배포는 12번입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 컨테이너로 빌드해서 확인하기 (podman) — 배포 전 점검
|
||||||
|
|
||||||
|
9번은 코드를 그냥 실행한 것이고, 실제 배포(Coolify)는 **Dockerfile 로 컨테이너를 빌드**해서 띄웁니다.
|
||||||
|
배포 전에 **같은 방식(컨테이너)으로 한 번 돌려보면** 배포 후 문제를 미리 잡을 수 있습니다.
|
||||||
|
(Coolify 도 똑같은 `Dockerfile` 을 쓰므로, **여기서 빌드가 되면 12번 배포 빌드도 거의 됩니다.**)
|
||||||
|
|
||||||
|
**1) 빌드**
|
||||||
|
```bash
|
||||||
|
cd ~/projects/<본인 프로젝트>
|
||||||
|
podman build -t myapp . # 현재 폴더의 Dockerfile 로 이미지 빌드
|
||||||
|
```
|
||||||
|
(`docker build ...` 도 동일하게 동작합니다 — 실제 런타임이 podman 일 뿐입니다.)
|
||||||
|
|
||||||
|
**빌드 로그 읽는 법** — 한 줄씩 `STEP 1/9`, `STEP 2/9` … 식으로 진행됩니다(이게 Dockerfile 의 각 명령). 마지막에
|
||||||
|
```
|
||||||
|
COMMIT myapp
|
||||||
|
Successfully tagged localhost/myapp:latest
|
||||||
|
<이미지ID>
|
||||||
|
```
|
||||||
|
가 보이면 **빌드 성공**입니다. 빌드된 이미지는 `podman images` 로 확인할 수 있습니다.
|
||||||
|
- **실패하면** 빨간 `Error:` 줄과 **몇 번째 STEP 에서 멈췄는지**를 보세요. 가장 흔한 건 `npm ci`/`npm install` 단계 실패(=의존성 문제, `package.json` 확인)입니다.
|
||||||
|
|
||||||
|
**2) 실행**
|
||||||
|
```bash
|
||||||
|
podman run --rm -p 3000:3000 --env-file .project-env myapp # 빌드한 이미지를 컨테이너로 실행
|
||||||
|
# 또는 (빌드+실행 한 번에): podman compose up --build
|
||||||
|
```
|
||||||
|
- `--env-file .project-env` 로 DB/S3 값을 컨테이너에 넣어줍니다(이게 없으면 컨테이너 안에서 `/db`·`/s3` 가 실패합니다).
|
||||||
|
- 기동되면 9번과 똑같이 `sample listening on :3000` 이 보입니다.
|
||||||
|
|
||||||
|
**3) 동작 확인** — 컨테이너 접속 확인은 **`127.0.0.1`** 로 하세요(`localhost` 가 간혹 안 잡힙니다). 기대 응답은 9번 표와 동일합니다:
|
||||||
|
```bash
|
||||||
|
curl 127.0.0.1:3000/healthz # {"ok":true}
|
||||||
|
curl 127.0.0.1:3000/db # {"ok":true,"now":"2026-..."}
|
||||||
|
curl 127.0.0.1:3000/s3 # {"ok":true,"bucket":"coolify-user-data",...}
|
||||||
|
```
|
||||||
|
- 브라우저로 보려면 9번과 동일하게 **PORTS 패널 → Forward Port 3000 → Open in Browser**(`https://<자동생성>.coder.bokdev.in`).
|
||||||
|
- 확인이 끝나면 터미널에서 **Ctrl+C** 로 컨테이너를 멈춥니다(`--rm` 이라 자동 삭제됨).
|
||||||
|
* 종료되지 않는 경우
|
||||||
|
```bash
|
||||||
|
podman ps
|
||||||
|
podman stop {이름 또는 ID}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 빌드/실행 권한은 워크스페이스에 이미 설정돼 있습니다(별도 설정 불필요).
|
||||||
|
|
||||||
|
> 여기서 **빌드 성공 + 3개 엔드포인트 모두 `ok:true`** 면 Coolify 배포도 거의 그대로 됩니다. 안 되면 코드/Dockerfile 을 먼저 고치세요.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Gitea(git)에 올리기
|
||||||
|
|
||||||
|
`new-project` 로 만든 폴더는 아직 git 저장소가 아닙니다(`.git` 없음). 아래처럼 올립니다.
|
||||||
|
(배포(12번)는 Gitea repo 를 받아서 빌드하므로, 배포 전에 반드시 올려야 합니다.)
|
||||||
|
|
||||||
|
> 아래 명령의 `myapp` 은 **본인이 `new-project` 로 만든 프로젝트 이름**으로 바꿔 쓰세요.
|
||||||
|
|
||||||
|
**1) Gitea에 빈 저장소 만들기**
|
||||||
|
- **https://gitea.bokdev.in** → 우측 상단 **`+` → New Repository**
|
||||||
|
- Repository Name 입력(예: `myapp`)
|
||||||
|
- **README/.gitignore/License 는 체크하지 마세요**(빈 저장소여야 충돌이 없습니다) → Create
|
||||||
|
- 생성되면 주소가 나옵니다: `https://gitea.bokdev.in/<본인사번>/myapp.git`
|
||||||
|
|
||||||
|
**2) 워크스페이스 터미널에서 올리기**
|
||||||
|
```bash
|
||||||
|
cd ~/projects/myapp
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "first commit(혹은 자유롭게)"
|
||||||
|
git branch -M main
|
||||||
|
git remote add origin https://gitea.bokdev.in/<본인사번>/myapp.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
- 인증은 자동입니다(6번 Gitea 승인을 한 번 했다면). 처음이면 승인 화면이 한 번 뜹니다.
|
||||||
|
- 이후 수정한 뒤에는 `git add . && git commit -m "..." && git push` 만 반복하면 됩니다.
|
||||||
|
|
||||||
|
> **순서는 상관없습니다.** 코드를 먼저 만들고(권장) 나중에 저장소를 만들어도 됩니다. `git push` 시점에 Gitea 저장소만 있으면 됩니다.
|
||||||
|
> **`.project-env` 는 git에 올라가지 않습니다**(DB·S3 자격증명 보호 — 정상). `git status` 에 안 보여도 맞습니다. 배포 환경값은 Coolify에 따로 넣습니다(12번).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Coolify로 배포하고 확인하기
|
||||||
|
|
||||||
|
운영(실제 서비스) 배포는 **Coolify**가 담당합니다. 흐름은 "**Gitea에 push → Coolify가 Dockerfile로 자동 빌드·배포**" 입니다. (먼저 11번으로 Gitea에 올려두세요.)
|
||||||
|
|
||||||
|
### 1) 앱(리소스) 만들기
|
||||||
|
|
||||||
|
1. **Coolify**(https://coolify.bokdev.in) 로그인.
|
||||||
|
2. **`aidev` 팀**으로 설정되어 있는지 확인.
|
||||||
|
3. 좌측 메뉴 **`Projects`** → **`aidev`**로 서버 및 DB가 연결된 프로젝트 환경 선택
|
||||||
|
4. `aidev` 프로젝트로 들어가면 환경(기본 **`production`**)이 보입니다. 거기서 **`+ New`** 클릭.
|
||||||
|
5. 리소스 종류 선택 화면에서 **`Public Repository`**(Git 기반 Public) 선택.
|
||||||
|

|
||||||
|
6. **Repository URL 은 반드시 전체 주소**로 입력: `https://gitea.bokdev.in/<본인사번>/myapp.git`
|
||||||
|
(`<본인사번>/myapp` 처럼 줄여 쓰면 clone 이 실패합니다.) `Check Repository`를 눌러 연결을 확인합니다.
|
||||||
|

|
||||||
|
7. **Build Pack** 을 **`Dockerfile`** 로 지정, Branch `main`, Port `3000`.
|
||||||
|
- (배포할 서버를 고르는 항목이 있으면 `localhost` 선택. 서버가 안 보이면 0번 팀 확인으로 돌아가세요.)
|
||||||
|
|
||||||
|
### 2) 도메인(공개 주소) 지정 — 앱의 **Configuration → Domains**
|
||||||
|
|
||||||
|
- **`Generate Domain`(자동 생성) 버튼을 누르면** `https://<랜덤>.apps.bokdev.in` 형태로 **자동 입력**됩니다
|
||||||
|
- 원하는 이름으로 바꾸려면 **`https://<원하는이름>.apps.bokdev.in`** 으로 직접 입력하세요(예: `https://myapp.apps.bokdev.in`).
|
||||||
|
- 반드시 **`.apps.bokdev.in` 로 끝나는** 주소여야 외부에서 https 로 열립니다(와일드카드 TLS·라우팅이 이 대역에만 준비돼 있음). `<이름>` 은 **남과 겹치지 않게**(예: `사번-앱이름`). 미리보기(9번)의 `*.coder.bokdev.in` 과는 별개 대역입니다.
|
||||||
|
|
||||||
|
### 3) 환경변수 입력 (앱마다 **최초 Deploy시 1회만**, 이후 배포엔 유지됨)
|
||||||
|
|
||||||
|
- Coolify의 **Environment Variables** 에 입력: `DATABASE_URL`, `S3_ENDPOINT`, `S3_REGION`, `S3_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `PORT`
|
||||||
|

|
||||||
|
- 가장 쉬운 방법: 워크스페이스 `.project-env` 파일을 열어 그 안의 값을 **`Developer view`에서 그대로 복사**해 넣으세요.
|
||||||
|

|
||||||
|
- 파일 보기: 터미널에서 `cat ~/projects/myapp/.project-env` (또는 VS Code 에서 그 파일 열기)
|
||||||
|
- `DATABASE_URL` 은 아래처럼 생겼습니다. **`%20`·`%3D` 까지 그대로** 넣으세요(libpq 인코딩이라 빼면 DB 연결이 깨집니다):
|
||||||
|
```
|
||||||
|
DATABASE_URL=postgresql://emp_<사번>:<pw>@10.200.0.152:5432/appdb?options=-c%20search_path%3Demp_<사번>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4) 배포(빌드) 실행 + 로그 보기
|
||||||
|
1. 앱 화면에서 **Deploy** 클릭.
|
||||||
|
2. **Deployments** 탭(또는 우측 알림)에서 방금 빌드를 누르면 **빌드 로그가 실시간으로** 흐릅니다. 단계는 대략:
|
||||||
|
- `Cloning ...` (Gitea 에서 코드 받기) → `Building image ...`(여러분의 `Dockerfile` 로 빌드, 10번 podman build 와 같은 STEP 들) → `Starting container ...` → **`New container started`** / `Deployment finished` 가 보이면 **배포 성공**.
|
||||||
|
3. **실패하면 로그의 빨간 줄**을 보세요. 위치로 원인이 갈립니다:
|
||||||
|
- `Cloning` 에서 실패 → Repository URL(전체 주소) 문제 (FAQ `does not appear to be a git repository`).
|
||||||
|
- `Building` 에서 실패 → Dockerfile/의존성 문제. **10번에서 `podman build` 가 됐다면 여기서도 거의 됩니다** → 안 되면 push 한 코드가 최신인지(11번) 확인.
|
||||||
|
- 빌드는 됐는데 컨테이너가 `Unhealthy`/재시작 반복 → 보통 Env 누락(아래 5번 확인).
|
||||||
|
|
||||||
|
### 5) 배포된 앱 주소 확인 + 테스트
|
||||||
|
1. 앱 주소는 **1)-3에서 지정한 `https://<원하는이름>.apps.bokdev.in`** 입니다(배포가 끝나면 그 주소로 외부에서 열립니다).
|
||||||
|
2. 그 주소로 **9·10번과 똑같이** 엔드포인트를 확인합니다(브라우저 또는 PC 터미널 `curl`):
|
||||||
|
```bash
|
||||||
|
curl https://<원하는이름>.apps.bokdev.in/healthz # {"ok":true} ← 앱 기동 OK
|
||||||
|
curl https://<원하는이름>.apps.bokdev.in/db # {"ok":true,"now":...} ← DB Env OK
|
||||||
|
curl https://<원하는이름>.apps.bokdev.in/s3 # {"ok":true,"bucket":...} ← S3 Env OK
|
||||||
|
```
|
||||||
|
- `/healthz` 만 되고 `/db`·`/s3` 가 500 이면 → **Coolify Env 문제**(2번). `.project-env` 값과 정확히 같은지(특히 `DATABASE_URL` 의 `%20`/`%3D`) 다시 확인하고 재배포.
|
||||||
|
3. 이후 코드를 고쳐 **`git push` 하면 자동으로 다시 빌드·배포**됩니다(Deployments 에서 새 빌드 로그 확인 → 같은 주소로 재확인).
|
||||||
|
|
||||||
|
> 자주 막히는 것: ① Repository URL 전체주소 ② `DATABASE_URL` host 가 `10.200.0.152` + 인코딩(`%20`/`%3D`) 그대로인지 ③ Env 를 앱에 저장했는지 ④ 도메인을 `.apps.bokdev.in` 으로 지정했는지. (FAQ 참고)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 워크스페이스 켜고 끄기
|
||||||
|
|
||||||
|
- **그만 쓸 때**: Coder 워크스페이스 화면 → **Stop** (자원 절약. 파일은 보존됩니다.)
|
||||||
|
- **다시 쓸 때**: **Start** (수십 초 내 기동)
|
||||||
|
- **업데이트 안내가 뜨면**(`Update` 버튼): 눌러서 최신 환경으로 갱신하세요. `~/projects` 파일은 유지됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 자주 묻는 것
|
||||||
|
- **Q. 파일이 사라졌어요** → `~/projects` 밖에 저장했을 가능성. 작업물은 항상 `~/projects` 아래에.
|
||||||
|
- **Q. AI 도구가 인증 오류(401)** → `update-litellm-key` 를 다시 실행해 본인 키를 입력하세요 (5번). 키가 `sk-` 로 시작하는지 확인.
|
||||||
|
- **Q. AI 도구가 `Invalid model` 오류(400)** → 인증은 됐지만 모델명이 게이트웨이에 없는 경우. 5번 표의 기본 모델명을 쓰세요.
|
||||||
|
- **Q. `$DATABASE_URL` 이 비어있어요** → 프로젝트 폴더 안에서 실행했는지 확인하세요. `.project-env` 는 그 폴더에 `cd` 해야 적용됩니다(7번).
|
||||||
|
- **Q. sample 을 고쳤는데 git pull 이 안 돼요** → sample 은 참조용(읽기 전용)입니다. `new-project <이름>` 으로 복사한 폴더에서 작업하세요(7번).
|
||||||
|
- **Q. git push가 인증을 물어봐요** → Gitea Authorize를 한 번도 안 했을 때. 6번 참고.
|
||||||
|
- **Q. Coolify 배포가 `does not appear to be a git repository` 로 실패해요** → Repository URL 을 전체 주소(`https://gitea.bokdev.in/...git`)로 넣었는지 확인하세요(12번).
|
||||||
|
- **Q. 배포한 앱에서 DB 연결이 안 돼요(`/db` 500)** → ① Coolify Env 의 `DATABASE_URL` host 가 `10.200.0.152` 인지, ② 끝의 `?options=-c%20search_path%3D...` 가 `%20`/`%3D` 까지 그대로인지 확인하세요. 직접 타이핑하다 인코딩을 빼면 깨집니다 — `.project-env` 값을 그대로 복사하는 게 가장 안전합니다(12번). 컨테이너명/내부 DNS 는 배포 환경에선 안 됩니다.
|
||||||
|
- **Q. `npm run dev` 가 `Cannot find package 'express'` 로 실패해요** → 처음 1회 `npm install` 을 안 한 경우입니다. 프로젝트 폴더에서 `npm install` 후 다시 실행하세요(7번).
|
||||||
|
- **Q. `podman build` 가 `npm ci`/`npm install` 단계에서 실패해요** → 의존성 문제입니다. 먼저 워크스페이스에서 `npm install` 이 되는지 확인하고, `package.json` 에 빠진 패키지가 없는지 보세요(10번).
|
||||||
|
- **Q. Coolify 빌드는 성공했는데 앱이 안 떠요(Unhealthy/재시작 반복)** → 보통 Env 누락입니다. `/healthz` 만 확인해 보고, DB/S3 Env(2번)를 `.project-env` 그대로 넣었는지 확인 후 재배포하세요(12번).
|
||||||
|
- **Q. Coolify 가 옛날 코드로 빌드돼요** → `git push` 가 됐는지(11번), Coolify 의 Branch 가 `main` 인지 확인하세요. push 후 Deploy(또는 자동배포)를 다시 도세요.
|
||||||
|
- **Q. 배포된 앱 주소가 안 열려요(접속 안 됨 / 인증서 경고)** → 도메인을 **`.apps.bokdev.in` 으로 끝나게** 지정했는지 확인하세요(12번 1)-3). 그 대역만 외부 https 가 준비돼 있습니다. 다른 대역(예: `*.bokdev.in` 루트, 임의 도메인)은 안 열리거나 인증서 경고가 납니다. 이름이 남과 겹쳐도 충돌하니 `사번-앱이름` 처럼 고유하게 정하세요.
|
||||||
|
- **Q. 빌드/실행은 됐는데 브라우저로 안 열려요** → 워크스페이스 앱은 `localhost` 가 PC 에서 안 열립니다. VS Code **PORTS → Forward 3000 → Open in Browser**(`https://<자동생성>.coder.bokdev.in`)로 여세요(9번). 배포된 앱은 Coolify 가 준 공개 주소로 엽니다(12번).
|
||||||
|
- **Q. DB가 비어있어요** → 정상입니다. 본인 전용 빈 스키마가 제공됩니다. 테이블은 직접 만들면 됩니다.
|
||||||
|
- **Q. K8s(쿠버네티스)는 어떻게 봐요?** → 직원은 K8s에 직접 접근하지 않습니다. DB/스토리지/배포는 위 도구들로 충분합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
문의: 인프라 담당자
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- TODO: md 미리보기로 보면 bash 입력인지 claude 입력인지 구분이 잘 안됨
|
||||||
2
PLAN.md
2
PLAN.md
@@ -137,7 +137,7 @@ distribution PATCH → task 폴링 → 배지 갱신 → 감사 로그 기록.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [ ] 단계 5 — 스타일 마감 + 폐쇄망 패키징
|
## [x] 단계 5 — 스타일 마감 + 폐쇄망 패키징 (배포: Coolify, 빌드 검증은 워크스페이스에서)
|
||||||
|
|
||||||
**목표:** 운영툴로서 보기 좋고, 인터넷 없이 그대로 구동/반입 가능.
|
**목표:** 운영툴로서 보기 좋고, 인터넷 없이 그대로 구동/반입 가능.
|
||||||
|
|
||||||
|
|||||||
87
README.md
Normal file
87
README.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Pulp 패치 관리 콘솔
|
||||||
|
|
||||||
|
공식 관리 UI가 없는 **Pulp 3**을 운영자가 클릭만으로 다루게 해주는 사내용 얇은 관리 콘솔.
|
||||||
|
핵심 가치: **검증이 끝난 특정 스냅샷(버전)만 운영 서버에 배포되도록 사람이 통제하는 화면.**
|
||||||
|
|
||||||
|
- 스택: **FastAPI + HTMX + Jinja2** (프론트 빌드 단계 없음), Pulp REST API를 **BFF가 프록시**.
|
||||||
|
- 브라우저는 Pulp를 직접 호출하지 않는다(인증정보·CORS 보호). 정적자산은 로컬 동봉(런타임 CDN 0개).
|
||||||
|
- 자세한 스펙은 `pulp-console-spec.md`, 단계별 진행은 `PLAN.md`, 디자인 기준은 `ref/`.
|
||||||
|
|
||||||
|
## 빠른 시작 (로컬)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
.venv/Scripts/python.exe -m pip install -r requirements-dev.txt # Windows
|
||||||
|
# (리눅스/맥: .venv/bin/pip install -r requirements-dev.txt)
|
||||||
|
|
||||||
|
# 실행
|
||||||
|
.venv/Scripts/python.exe -m uvicorn app.main:app --reload # http://127.0.0.1:8000
|
||||||
|
# 테스트 / 린트
|
||||||
|
.venv/Scripts/python.exe -m pytest -q
|
||||||
|
.venv/Scripts/python.exe -m ruff check app tests
|
||||||
|
```
|
||||||
|
|
||||||
|
**데모 모드** — 실제 Pulp 없이 화면을 보려면:
|
||||||
|
```bash
|
||||||
|
PULP_DEMO=true .venv/Scripts/python.exe -m uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## 환경변수
|
||||||
|
|
||||||
|
| 변수 | 용도 | 비고 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PULP_BASE_URL` | Pulp API 주소 | 예 `http://repo.internal:8080` |
|
||||||
|
| `PULP_USERNAME` / `PULP_PASSWORD` | Pulp Basic Auth | 비밀번호는 git에 두지 않음 |
|
||||||
|
| `PULP_VERIFY_TLS` | TLS 검증 on/off | 사내 self-signed면 `PULP_CA_FILE` 사용 |
|
||||||
|
| `PULP_CA_FILE` | 사내 CA(.pem) 경로 | HTTPS Pulp + 사내 CA일 때 |
|
||||||
|
| `DATABASE_URL` | 감사 로그용 Postgres | 미설정 시 배포는 되나 감사 기록 실패 경고 |
|
||||||
|
| `PORT` | 서버 포트 | 기본 8000 |
|
||||||
|
| `PULP_DEMO` | 데모 모드 | 기본 off |
|
||||||
|
|
||||||
|
## 헬스 / 상태
|
||||||
|
|
||||||
|
- `GET /healthz` — 앱 라이브니스. Pulp와 무관하게 항상 `{"ok": true}` (컨테이너 health check용).
|
||||||
|
- `GET /pulp-status` — Pulp 연결 상태 배지(대시보드가 폴링).
|
||||||
|
|
||||||
|
## 컨테이너 빌드 (배포 전 점검 — MANUAL §10)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman build -t pulp-console .
|
||||||
|
podman run --rm -p 8000:8000 \
|
||||||
|
-e PULP_BASE_URL=... -e PULP_USERNAME=... -e PULP_PASSWORD=... \
|
||||||
|
-e DATABASE_URL="$DATABASE_URL" pulp-console
|
||||||
|
curl 127.0.0.1:8000/healthz # {"ok":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coolify 배포 (MANUAL §11–12)
|
||||||
|
|
||||||
|
1. Gitea에 push.
|
||||||
|
2. Coolify에서 **Public Repository** → repo 전체 URL → **Build Pack: `Dockerfile`**, Branch `main`, **Port `8000`**.
|
||||||
|
3. **Environment Variables**에 위 표의 값 입력(`PULP_*`, `DATABASE_URL`, `PORT`). 비밀번호/`DATABASE_URL`은 여기에만.
|
||||||
|
4. 도메인은 `*.apps.bokdev.in`으로 지정 → Deploy.
|
||||||
|
|
||||||
|
## 감사 로그 (배포 기록)
|
||||||
|
|
||||||
|
배포 확정은 Postgres `deploy_audit` 테이블에 기록된다(누가/언제/repo/이전→대상 버전, 롤백 추적).
|
||||||
|
테이블은 최초 기록 시 자동 생성(`CREATE TABLE IF NOT EXISTS`). `DATABASE_URL` 필요.
|
||||||
|
|
||||||
|
## 폐쇄망 반입 (최종 산출물)
|
||||||
|
|
||||||
|
정적자산(Pico/htmx/Pretendard)은 이미 `app/static/`에 동봉되어 인터넷 없이 동작한다.
|
||||||
|
Python 의존성만 오프라인으로 넣으면 된다 — 인터넷 가능 리눅스에서 wheelhouse를 받아 동봉:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip download -r requirements.txt -d vendor/ \
|
||||||
|
--platform manylinux2014_x86_64 --python-version 313 --only-binary=:all:
|
||||||
|
```
|
||||||
|
|
||||||
|
그리고 `Dockerfile`의 설치 단계를 오프라인으로 교체:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
COPY requirements.txt .
|
||||||
|
COPY vendor/ ./vendor/
|
||||||
|
RUN pip install --no-cache-dir --no-index --find-links=vendor -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
> 현재(데모) 배포는 Coolify가 빌드 시 의존성을 받는 `pip install` 방식이다.
|
||||||
|
> Coolify 빌드에서 PyPI 접근이 막히면 위 wheelhouse 방식으로 전환한다.
|
||||||
Reference in New Issue
Block a user