08. 설치 · 실행 · 배포
요구 사항
- Node.js 20+ (개발 환경 24.x), npm
- Python 3.12+
- PostgreSQL 14+ (운영 16). 로컬은
createuser acn && createdb -O acn acn - 모바일 기기 + TokenPocket 또는 MetaMask 앱 (PC와 같은 네트워크)
로컬 실행
백엔드
cd backend
python -m venv .venv
.venv\Scripts\activate # macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt
copy .env.example .env # macOS/Linux: cp
# .env의 DATABASE_URL을 자기 DB로 맞춘 뒤 실행한다
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
--host 0.0.0.0로 띄워야 휴대폰에서 접근할 수 있다. .env의 CORS_ORIGINS에
http://<PC-IP>:3000을 추가한다.
인덱서(선택): python -m app.indexer
프론트엔드
cd frontend
npm install
copy .env.example .env.local
# .env.local: NEXT_PUBLIC_API_URL=http://<PC-IP>:8000
npm run dev -- -H 0.0.0.0
휴대폰 지갑 앱 dApp 브라우저에서 http://<PC-IP>:3000 접속.
PC에서 UI만 확인
Chrome DevTools → Device toolbar(모바일 UA + 터치 에뮬레이션). 지갑이 없으므로 딥링크 화면까지만 보인다. 실제 서명 테스트는 반드시 기기에서 한다.
백엔드 배포 — Render (Blueprint)
저장소 루트의 render.yaml이 FastAPI 백엔드를 정의한다(서비스명 acn202610-api, 루트 디렉터리 backend, Python 3.12, 헬스체크 /health).
- https://dashboard.render.com → New → Blueprint → GitHub 저장소
kimduckbo/acn선택(비공개 저장소이므로 Render GitHub 앱에 접근 권한 부여) → Apply. - 배포가 끝나면
https://acn202610-api.onrender.com/health가{"status":"ok"}를,/api/indexer가 동기화 상태를 반환하는지 확인. - Render가 다른 호스트명을 배정했다면
netlify.toml의NEXT_PUBLIC_API_URL을 그 값으로 바꿔 푸시(또는 Netlify UI 환경 변수로 덮어쓰기). - Render 서비스의
CORS_ORIGINS에 프론트 오리진(https://acn202610.netlify.app)이 들어 있는지 확인(Blueprint 기본값에 포함).
무료 플랜 특성:
- 15분 동안 요청이 없으면 인스턴스가 잠들고, 첫 요청에 30~60초가 걸린다. dApp의 내역 조회는 404/오류 시 재시도하므로 잠시 후 표시된다.
- 영구 디스크가 없다. Render에 올릴 경우
DATABASE_URL을 Render Postgres 등 외부 관리형 DB로 지정해야 한다. Office 원장은 온체인에서 재구축할 수 없으므로 인스턴스 파일시스템에 두면 안 된다. - 백그라운드 워커가 유료이므로
RUN_INDEXER_IN_APP=true로 인덱서를 API 프로세스 안에서 데몬 스레드로 돌린다. 웹 인스턴스는 1개만 유지한다(여러 개면 인덱서가 중복 실행됨).
현재 운영 배포 — app.analyze.kr (자체 서버)
2026-09-08부터 AWS EC2 한 대에서 nginx 뒤로 프론트·백엔드·DB를 모두 돌린다. Netlify/Render 설정은 저장소에 남아 있지만 사용하지 않는다.
인터넷 ─443/TLS─▶ nginx ─┬─ /api/ ─▶ 127.0.0.1:8000 acn-api (uvicorn) ─▶ PostgreSQL 16 (db: acn)
└─ / ─▶ 127.0.0.1:3000 acn-web (next start)
| 구성 요소 | 위치 |
|---|---|
| 리버스 프록시 | /etc/nginx/sites-available/acn.analyze.kr (파일명은 최초 도메인 그대로), 80→443 301 |
| TLS | Let's Encrypt, certbot.timer가 자동 갱신. 재발급은 acn-ssl |
| 백엔드 | acn-api.service — uvicorn --host 127.0.0.1 --port 8000 --proxy-headers --forwarded-allow-ips=127.0.0.1 |
| 프론트엔드 | acn-web.service — npm run start -- -H 127.0.0.1 -p 3000 |
| DB | 로컬 PostgreSQL 16, DB acn, 역할 acn. 접속 문자열은 backend/.env의 DATABASE_URL |
| 백업 | /etc/cron.d/acn-backup → acn-backup 매일 03:17 UTC, 검증은 acn-restore-check |
- 3000·8000은 loopback에만 바인딩한다. 공개 진입점은 nginx뿐이라 인증서를 우회한 평문 HTTP로 사이트가 노출되지 않는다.
- uvicorn의
--proxy-headers가 필수다. 백엔드는request.client.host로 레이트리밋 키를 만드는데, 이게 없으면 모든 요청이 nginx의127.0.0.1한 버킷에 담겨 "IP당 분당 20회"가 전체 합쳐 20회가 된다. nginx는X-Forwarded-For를 넘긴다. NEXT_PUBLIC_*은 빌드 시점에 번들에 박힌다. 값을 바꾸면npm run build후systemctl restart acn-web까지 해야 반영된다.- 인바운드 80/443이 보안 그룹에서 열려 있어야 한다. 서버 자체 방화벽(
ufw)은 쓰지 않는다. acn.analyze.kr은 쓰지 않는다. 같은 서버가 IP로도,app.analyze.kr로도 정상 응답하는데 이 이름으로만 연결이 끊기는 네트워크가 있어(이름 기반 필터링) 정식 도메인을 옮겼다. 옛 이름은 인증서에 남겨 두고https://app.analyze.kr$request_uri로 301만 한다 — 리다이렉트도 TLS 핸드셰이크 를 먼저 끝내야 하므로 인증서가 필요하다.- Next.js 업스트림에는
X-Forwarded-Proto를 보내지 않는다. 보내면 Next가 스킴만 https로 바꾸고 호스트는 내부값을 유지해NextResponse.rewrite()가https://localhost:3000/...을 만들고, 평문 포트에 TLS로 접속하다 500이 난다(EPROTO). 데스크톱에서/{locale}/dapp이 이걸로 깨졌었다. API 업스트림에는 계속 보낸다(uvicorn이 실제 클라이언트 IP 판별에 쓴다). - 도달성 확인용으로 nginx가 직접 응답하는
/probe-f60f9557경로를 열어 두었다. 앱·API·DB와 무관하게 요청이 서버까지 왔는지만 가린다.
systemctl restart acn-api # backend/.env 수정 후
journalctl -u acn-api -f # 로그
acn-backup && acn-restore-check
프로덕션 배포(권장 구성)
| 구성 요소 | 방법 |
|---|---|
| 프론트엔드 | npm run build 후 Node 서버(npm start) 또는 Vercel. 반드시 HTTPS (지갑 앱 다수가 http dApp 차단) |
| 백엔드 | uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2 를 systemd로 관리, Nginx/Caddy 리버스 프록시 + TLS |
| 인덱서 | 별도 systemd 서비스로 python -m app.indexer, 자동 재시작 |
| DB | PostgreSQL 16. DATABASE_URL로 지정하고 pg_dump로 주기 백업 + 복원 검증(05-database.md) |
| RPC | bsc-dataseed.binance.org는 eth_getLogs를 거부하므로 인덱서에 쓸 수 없다. 기본값은 bsc-rpc.publicnode.com이며, 운영은 NodeReal/QuickNode/Ankr 등 전용 RPC 권장 |
환경 변수 체크리스트
-
DATABASE_URL= PostgreSQL 접속 문자열(비밀번호 포함 →.env는 0600) -
NEXT_PUBLIC_API_URL= 백엔드 공개 HTTPS URL -
CORS_ORIGINS= 프론트 공개 HTTPS 오리진 -
NEXT_PUBLIC_TOKEN_ADDRESS==TOKEN_ADDRESS -
NEXT_PUBLIC_CHAIN_ID==CHAIN_ID - (선택)
NEXT_PUBLIC_WC_PROJECT_ID - 저장소 루트의
.env류 파일이 커밋되지 않았는지 확인(.gitignore적용)
검증 명령
cd frontend && npm run lint && npm run build
cd backend && .venv/bin/python -c "import app.main, app.indexer" # .venv\Scripts\python on Windows
curl http://localhost:8000/health
curl http://localhost:8000/api/token
# Office 전 기능 스모크 테스트. 대상 DB를 **지우므로** 반드시 *_test DB를 가리킬 것
cd backend && DATABASE_URL=postgresql://acn:PW@127.0.0.1/acn_test .venv/bin/python tests_office_smoke.py
