본문으로 건너뛰기

Python FastAPI 운영 가이드

💡 요약 정리

  • Python 3.12 + FastAPI ≥0.115 + uvicorn ≥0.32 + uv 0.5+ + systemd 환경의 운영 매뉴얼입니다.
  • DB는 PostgreSQL 17 또는 MariaDB 11.4 중 선택하여 사용할 수 있습니다.
  • 앱은 127.0.0.1:8000에서 listen하며, 외부 접속은 nginx 리버스 프록시를 통해 처리됩니다.
  • 배포 시 systemd unit 파일과 env 파일은 절대 업로드하지 마세요.

1. 환경 매니페스트

항목
OSUbuntu 24.04 LTS
언어 / 런타임Python 3.12
프레임워크FastAPI ≥0.115 + uvicorn ≥0.32
패키지 매니저uv 0.5+
프로세스 매니저systemd
앱 listen127.0.0.1:8000 (외부 직접 접근 차단)
리버스 프록시nginx 1.24 (:80/:443, Let's Encrypt 자동)
앱 디렉토리/opt/[프로젝트] (appuser:appuser 0750)
환경변수/etc/[프로젝트]/env (root:appuser 0640)
로그/var/log/[프로젝트]
서비스 단위[프로젝트].service
주 코드/opt/[프로젝트]/main.py (FastAPI app 객체)

DB 매니페스트 (선택 옵션)

항목PostgreSQL 17MariaDB 11.4
인증peer auth (sudo psql)unix_socket auth (sudo mariadb)
포트54323306
서비스명postgresql@17-mainmariadb
백업pg_dumpmariadb-dump
클라이언트 라이브러리psycopg[binary] 또는 asyncpgpymysql 또는 aiomysql

예시: 프로젝트 이름 변수 설정
본 가이드의 [프로젝트]는 자리 치환입니다. 셸에 다음 한 줄을 박아두시면 그대로 복사·붙여넣기가 가능합니다.

PROJECT_NAME=myapp; export PROJECT_NAME

2. 서버 접속

2-1. SSH (root)

ssh root@아이디.mycafe24.com

2-2. SFTP / rsync (코드 업로드)

appuser는 SSH 키 미설정. root로 업로드 후 chown하시는 방법을 권장합니다.

STEP명령
업로드 (rsync)rsync -avz --exclude='.venv/' --exclude='__pycache__/' ./ root@아이디.mycafe24.com:/opt/$PROJECT_NAME/
권한 회복ssh root@아이디.mycafe24.com "chown -R appuser:appuser /opt/$PROJECT_NAME"

2-3. appuser로 작업 (디버깅)

작업명령
전환sudo -u appuser bash
이동cd /opt/$PROJECT_NAME

3. 환경 확인

확인 항목명령
디렉토리 확인ls -la /opt/$PROJECT_NAME
환경변수 파일sudo cat /etc/$PROJECT_NAME/env
서비스 활성sudo systemctl is-active $PROJECT_NAME
서비스 상세sudo systemctl status $PROJECT_NAME
로그 (최근 50줄)sudo journalctl -u $PROJECT_NAME -n 50
포트 listen (127.0.0.1만 정상)ss -tlnp | grep 8000
헬스 체크 (서버 내부)curl -sf http://127.0.0.1:8000/
외부 도메인 응답curl -sI https://아이디.mycafe24.com/
uvicorn 워커 확인ps aux | grep uvicorn

4. DB 직접 접속

4-1. PostgreSQL 17 — 슈퍼유저 (peer auth)

sudo -u postgres psql
작업명령
DB 목록\l
사용자 목록\du
DB 전환\c [DB명]
테이블 목록\dt
종료\q

4-2. MariaDB 11.4 — 슈퍼유저 (unix_socket auth)

sudo mariadb
작업명령
DB 목록SHOW DATABASES;
사용자 목록SELECT user, host FROM mysql.user;
DB 전환USE [DB명];
테이블 목록SHOW TABLES;
종료EXIT;

4-3. 앱 사용자로 접속 (TCP)

DB접속 명령
PostgreSQLpsql "postgresql://[DB사용자]:[비밀번호]@127.0.0.1:5432/[DB명]"
MariaDBmariadb -u [DB사용자] -p [DB명]
비밀번호는 /etc/[프로젝트]/envDB_PASSWORD 값을 사용하시면 됩니다.

5. 코드 배포 워크플로우

5-A. 배포 시 위험 회피

자체 코드를 통째로 업로드하면 자동 구성된 DB · 보안 · systemd 설정이 사라져 서비스가 깨질 수 있습니다. 아래 위험 요소를 반드시 확인하신 후 배포를 진행해 주세요.

위험 5종 매트릭스

#위험증상FastAPI 안전 패턴
1DB 환경변수 참조 깨짐HTTP 200은 떠도 모든 DB 쿼리 실패 (KeyError, NameError)os.environ["DATABASE_URL"] 또는 os.environ.get("DATABASE_URL") 코드에서 그대로 사용
2외부 IP에 listen외부에서 :8000 직접 접속 가능 → nginx 우회uvicorn --host 127.0.0.1 (systemd unit ExecStart)
3엔트리 포인트 변경systemd가 시작 시 파일 못 찾음 ("could not find")/opt/[프로젝트]/main.py + app = FastAPI() 위치 유지
4의존성 동기화 누락"ImportError" / "ModuleNotFoundError"업로드 후 uv sync 또는 uv add <패키지>
5systemd unit 덮어씀env 변수 주입 안 됨 → DB 연결 실패[프로젝트].service 절대 업로드 대상 포함 금지

안전한 코드 패턴

main.py에 다음 핵심 요소를 그대로 유지하시면 됩니다.

요소코드
importfrom fastapi import FastAPI + import os
앱 객체 (이름 유지 필수)app = FastAPI()
환경변수 참조DATABASE_URL = os.environ["DATABASE_URL"]
루트 라우트@app.get("/") + def root(): return {"status": "ok"}

절대 업로드 금지 파일

  • /etc/systemd/system/[프로젝트].service
  • /etc/[프로젝트]/env

3가지 배포 방법 (안전 순서)

방법설명적합
1. 변경 파일만 업로드rsync로 .py 파일만 (가장 안전)일상 배포
2. 소스 통째 + 서버 빌드.venv/, __pycache__/ 제외 필수큰 변경, 의존성 추가
3. 외부 설정 분리DB 등은 env 파일만, 코드는 자유권장 베이스

배포 후 4가지 체크

#확인 사항명령
1활성 상태sudo systemctl is-active $PROJECT_NAME
2127.0.0.1 listenss -tlnp | grep 8000 | grep '127.0.0.1'
3헬스 OKcurl -sf http://127.0.0.1:8000/health
4외부 응답curl -sI https://[도메인]/

5-B. FastAPI 배포 7단계

STEP작업명령
1빌드FastAPI는 인터프리터 — 빌드 단계 없음
2업로드 (rsync)rsync -avz --exclude='.venv/' --exclude='__pycache__/' ./ root@아이디.mycafe24.com:/opt/$PROJECT_NAME/
3권한 회복chown -R appuser:appuser /opt/$PROJECT_NAME
4의존성 동기화sudo -u appuser uv sync --directory /opt/$PROJECT_NAME
5마이그레이션 (Alembic 등 사용 시)env 로드 후 uv run alembic upgrade head
6재시작sudo systemctl restart $PROJECT_NAME
7검증sudo journalctl -u $PROJECT_NAME -n 30 -f

자주 만나는 실수

실수결과회복
.venv/ 통째 업로드다른 Python 버전 .venv가 덮어씌워져 ImportErrorrm -rf /opt/$PROJECT_NAME/.venv && sudo -u appuser uv sync
app 객체명 변경systemd 시작 실패main.pyapp = FastAPI() 복원
Pydantic 동기화 누락ValidationError모델 import 경로 확인, restart

5-C. 환영 페이지 끄고 내 코드 띄우기 (필독)

설치 직후 도메인으로 접속하면 "서버가 정상 동작 중입니다" 환영 페이지가 표시됩니다. 이는 Nginx가 /var/www/cafe24-welcome/index.html을 우선 서빙하기 때문입니다. 본인 코드의 / 라우트가 보이게 하려면 이 환영 파일 1개만 정리하면 됩니다.

5-C-1. 환영 페이지 끄기

방법명령
방법 A — 삭제sudo rm /var/www/cafe24-welcome/index.html
방법 B — 백업 후 비활성
(원복 가능)
sudo mv /var/www/cafe24-welcome/index.html /var/www/cafe24-welcome/index.html.bak
현재 상태 확인ls /var/www/cafe24-welcome/

5-C-2. 확인

도메인 재접속 또는 curl -sI https://[도메인]/ 결과로 본인 앱이 응답하는지 확인합니다. 캐시가 남으면 시크릿 창 또는 강제 새로고침을 권장합니다.

Tip: 환영 파일을 백업해두면 트러블슈팅 시 "Nginx는 살아있나" 확인용으로 다시 활성화할 수 있습니다.


6. DB 연동 실전 코드

SQLAlchemy 2.x + asyncpg (PostgreSQL)

db.py에 다음 요소를 그대로 정의하시면 됩니다.

요소코드
importfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker + import os
URLDATABASE_URL = os.environ["DATABASE_URL"]
enginecreate_async_engine(DATABASE_URL, pool_size=5, max_overflow=10, pool_pre_ping=True)
sessionasync_sessionmaker(engine, expire_on_commit=False)

SQLAlchemy 2.x + aiomysql (MariaDB)

DATABASE_URL을 mysql+aiomysql://user:pass@127.0.0.1:3306/dbname?charset=utf8mb4 형식으로 사용하시면 됩니다. 나머지 코드는 동일합니다.

CRUD 라우터 예시

FastAPI + Depends 기반 비동기 세션 패턴입니다.

구성코드
의존성from fastapi import FastAPI, Depends + from sqlalchemy import select + from sqlalchemy.ext.asyncio import AsyncSession
세션 generatorasync def get_session(): async with AsyncSessionLocal() as session: yield session
list 라우트@app.get("/items") + async def list_items(session: AsyncSession = Depends(get_session)):
쿼리 실행result = await session.execute(select(Item))
응답return result.scalars().all()

7. 정적 파일 / 미디어

FastAPI는 정적 파일을 nginx에 직접 위임하는 패턴이 권장됩니다. nginx 설정 파일에 /static/·/media/ location을 추가하시고, 변경 후 아래 명령으로 적용해 주세요.

sudo nginx -t && sudo systemctl reload nginx

8. 로그 / 모니터링

로그 종류명령
실시간 (systemd)sudo journalctl -u $PROJECT_NAME -f
최근 100줄sudo journalctl -u $PROJECT_NAME -n 100
1시간 이내sudo journalctl -u $PROJECT_NAME --since "1 hour ago"
에러만sudo journalctl -u $PROJECT_NAME -p err
nginx accesssudo tail -f /var/log/nginx/[프로젝트]_access.log
nginx errorsudo tail -f /var/log/nginx/[프로젝트]_error.log

9. HTTPS / 도메인

9-1. 무료 도메인 (자동)

아이디.mycafe24.com은 SSL 자동 — 별도 작업 불필요합니다.

9-2. 보유 도메인 SSL

보유 도메인의 HTTPS 적용은 SSL 별도 구매 필요합니다. 카페24 SSL 인증서를 구매하신 후 나의 서비스 관리에서 설치해 주세요.


10. 트러블슈팅 + FAQ

트러블슈팅 매트릭스

증상원인해결
HTTP 502 Bad Gatewayuvicorn 다운 / 포트 미스매치systemctl status $PROJECT_NAME + journalctl -u $PROJECT_NAME -n 50
ImportError / ModuleNotFoundError의존성 미동기화sudo -u appuser uv sync + restart
KeyError: 'DATABASE_URL'env 미주입 (수동 실행)set -a; source /etc/$PROJECT_NAME/env; set +a 후 실행
Connection refused (DB)DB 미기동PG: systemctl status postgresql@17-main / Maria: systemctl status mariadb
password authentication failedenv 비밀번호 불일치sudo cat /etc/$PROJECT_NAME/env로 확인, 매뉴얼 로그인 시도
Worker timeout (503)동기 블로킹async/await 누락 검토

FAQ

질문답변
uv add 후 ImportError가 안 사라져요systemd 프로세스가 import 캐시 보유. restart 전 uv sync 실행 필수
uvicorn 직접 실행 시 KeyErrorsystemd 컨텍스트 외부에선 env 미주입. 수동 실행 시 set -a; source /etc/$PROJECT_NAME/env; set +a 후 명령 실행
외부에서 :8000으로 접속 안 됩니다의도된 동작입니다. 외부 노출은 nginx :80/:443. ss -tlnp | grep 8000127.0.0.1만 보이면 정상
/etc/[프로젝트]/env에 평문 비밀번호 노출 우려모드 0640 root:appuser. systemd EnvironmentFile이 부팅 시 주입. 더 강한 격리 원하시면 sops/vault 사용 가능

11. 추가 운영 / 커스터마이징

nginx 변경 안전 시퀀스, DB 백업, 보안, 성능 튜닝 등 공통 운영 사항은 공통 운영·커스터마이징 가이드 페이지를 참조해 주세요. 요금제·SSL·도메인·트래픽 등 일반 문의는 FAQ · 트러블슈팅 페이지를 확인하시면 됩니다.

추가적인 도움이 필요하시면 1:1 문의게시판을 통해 문의해 주세요.