ACN
dAppOffice
문서 목차 (8 / 16)

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로 띄워야 휴대폰에서 접근할 수 있다. .envCORS_ORIGINShttp://<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).

  1. https://dashboard.render.comNew → Blueprint → GitHub 저장소 kimduckbo/acn 선택(비공개 저장소이므로 Render GitHub 앱에 접근 권한 부여) → Apply.
  2. 배포가 끝나면 https://acn202610-api.onrender.com/health{"status":"ok"}를, /api/indexer가 동기화 상태를 반환하는지 확인.
  3. Render가 다른 호스트명을 배정했다면 netlify.tomlNEXT_PUBLIC_API_URL을 그 값으로 바꿔 푸시(또는 Netlify UI 환경 변수로 덮어쓰기).
  4. 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
TLSLet's Encrypt, certbot.timer가 자동 갱신. 재발급은 acn-ssl
백엔드acn-api.serviceuvicorn --host 127.0.0.1 --port 8000 --proxy-headers --forwarded-allow-ips=127.0.0.1
프론트엔드acn-web.servicenpm run start -- -H 127.0.0.1 -p 3000
DB로컬 PostgreSQL 16, DB acn, 역할 acn. 접속 문자열은 backend/.envDATABASE_URL
백업/etc/cron.d/acn-backupacn-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 buildsystemctl 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, 자동 재시작
DBPostgreSQL 16. DATABASE_URL로 지정하고 pg_dump로 주기 백업 + 복원 검증(05-database.md)
RPCbsc-dataseed.binance.orgeth_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