L01 — Django 프로젝트 구조와 settings 환경 분리 (수정중)
base / local / production 으로 갈리는 이유와, 요청이 들어올 때 어떤 설정이 로드되는지 설명할 수 있다.1. 파일 주고받기 — SFTP / Cyberduck
로컬 컴퓨터와 서버 사이에 파일을 주고받는 방법부터.
- FTP(File Transfer Protocol) / SFTP(Secure FTP) = 파일을 주고받을 때 컴퓨터끼리 지키는 약속(프로토콜)이다.
- Cyberduck 같은 클라이언트(연결 프로그램)로 SFTP 연결을 하면, 로컬 → 서버로 파일을 올리고(업로드) 서버 → 로컬로 파일을 받을(다운로드) 수 있다.
- 예전에는 업로드가 안 돼서 Cyberduck 을 꼭 써야 했지만, 지금은 VS Code(원격 연결)로 다운로드·업로드가 다 된다. 그래서 요즘은 Cyberduck 을 잘 안 쓴다.
- FTP 는 코드 안에서도 서버 → 다른 서버나 다른 IP 로 파일을 보낼 때 쓸 수 있다. "이런 프로토콜이 있다" 정도만 알아두면 된다.
2. 서버에 있는 standarda-template / standarda-core
- 서버 홈 디렉토리(
ubuntu)에standarda-template,standarda-core폴더가 실제로 있다. cookiecutter(프로젝트 뼈대를 찍어내는 도구)로 새 프로젝트를 만들 때는 보통 GitHub 의 standarda-template 저장소(레포)를 당겨온다. (서버 로컬에 있는 것도 당길 수 있지만, 헷갈릴까 봐 로컬 사본은 지워뒀다.)- standarda-template 업데이트 방법: 서버에서 standarda-template 코드를 수정한 뒤(스킬·백로그 등 추가)
push한다. 변화가 생기면 자동으로 push 되게 해뒀기 때문에 항상 최신 버전이 유지된다.
3. Django 프로젝트 구조 — 앱들 + 루트(설정) 앱
템플릿 구조 = 우리 프로젝트 구조다. (.claude/, GitHub 설정, 그리고 cookiecutter 의 {project_slug} 폴더)
- Django 는 백엔드(서버 쪽) 프레임워크다 — 웹 서버를 만들 때 쓰는 뼈대 틀이다. 그 안에서 폴더 구조가 "앱(app)" 단위로 나뉜다.
- 예를 들어
accounts,export_web,emails,profiles,projects,sms— 이게 전부 Django 앱이다. - 프로젝트를 처음 셋업하면 프로젝트 이름과 똑같은 이름의 앱 폴더가 하나 생긴다(예:
export_web). - 이 루트 앱은 다른 앱들과 달리 한 가지가 더 있다 —
settings폴더를 가지고 있다.
{프로젝트명}/ ← 루트 앱 (프로젝트 이름과 동일)
├── settings/ ← ★ 이 앱에만 있다
├── urls.py
├── wsgi.py
└── ...
accounts/ profiles/ projects/ emails/ sms/ export_web/ ← 일반 Django 앱들
4. settings 는 파일이 아니라 "패키지"
- 원래 Django 의 settings 는
settings.py파일 하나다. - 우리는 이걸 파이썬 패키지(폴더 +
__init__.py)로 만들었다. 폴더 안에는:
settings/
├── __init__.py # 환경에 따라 무엇을 로드할지 고르는 곳
├── base.py # 공통 설정
├── local.py # 개발 환경
└── production.py # 운영 환경
5. __init__.py — 환경에 따라 무엇을 로드하나
# {프로젝트명}/settings/__init__.py
if os.environ.get('{프로젝트명대문자}_PRODUCTION') == 'True':
load_dotenv(_BASE_DIR / '.env.production', override=True)
else:
load_dotenv(_BASE_DIR / '.env', override=True)
from .base import * # 공통을 먼저 다 가져옴
if os.environ.get('{프로젝트명대문자}_PRODUCTION') == 'True':
from .production import * # 운영이면 production 으로 덮어씀
else:
from .local import * # 아니면 local
- 로직은 단순하다: PRODUCTION 이면
base + production, 아니면base + local. _BASE_DIR: 파이썬pathlib의Path로src/(루트) 폴더를 가리킨다.Path(__file__)에서 부모를 세 번 거슬러 올라가면src/다.src/가 항상 프로젝트의 루트(베이스) 폴더다.from .base import *의*는 와일드카드 — 해당 모듈에 있는 모든 값을 다 가져온다는 뜻이다.
6. .env / .env.example
.env.example: 템플릿을 당기면 같이 생성된다. "이 형식대로 채워 넣어라" 는 견본이다.- 이걸 복사해서
.env를 만들고, API 키 ·ALLOWED_HOSTS· 비밀번호 등을 쫙 채워서 쓴다. .env.production.example도 있다. 나중에 배포(deploy) 할 때 이걸 복사해.env.production을 만든다. (지금은 비어 있다.)
비밀값(키·비번)은 settings 코드가 아니라
.env(개발) /.env.production(운영) 에 둔다.
7. 왜 dev / prod 를 나누나
.env 와 production 을 구분하는 이유는, 개발 환경과 운영 환경이 실제로 다르기 때문이다. 연습할 때 쓰는 책상과 실제 손님을 받는 책상이 다른 것과 같다 — 같은 코드라도 어디에 붙이느냐에 따라 연결할 데가 달라진다.
- 서버 IP 가 다르다: 개발 서버는
43.200.38.93, 앞으로 Production 서버는52.79.132.206에서 관리한다. - 그래서
ALLOWED_HOSTS주소부터 달라진다. - DB 가 다르다: Django 프로젝트는 기본적으로 PostgreSQL 과 연결돼 있다. 개발 서버에는 개발용(로컬) PostgreSQL 이, Production 서버에는 운영용 PostgreSQL 이 깔려 있고, 각각 연결해야 한다.
- API 키가 다르다: 예를 들어 LLM API 키도 개발용 키는 우리 키, 운영용 키는 고객사 키가 된다.
→ 그래서 환경마다 달라지는 값들을 따로따로 관리한다.
8. 요청이 들어오면 무슨 일이 — settings 로딩 흐름
브라우저 (도메인 입력)
→ DNS 가 도메인 → IP 조회 (예: meetings.popupstudio.ai → 43.200.38.93)
→ 그 서버의 포트로 요청 도착 (현재 dev 서버에 배포됨, 예: :8024 에서 Django 가 대기)
→ Django 가 settings/__init__.py 를 "제일 먼저" 읽음
→ os.environ 에서 {프로젝트명대문자}_PRODUCTION 확인
→ 없으면 else → .env + base + local
→ 있으면(True) → .env.production + base + production
os.environ.get(...) 은 시스템(OS) 환경변수를 읽는다. 환경변수는 프로그램이 실행될 때 참고하라고 OS 쪽에 미리 적어둔 설정값이다. 그러면 그 값은 어떻게 넣어두나? 넣는 방법은 크게 세 가지다.
방법 ① OS 의 /etc/environment 에 박아두기
우분투 OS 전역 환경변수 파일이다. production 서버 OS 에는 박아두고, dev 서버 OS 에는 비워둔다. 그러면 서버 단위로 dev / production 이 갈린다 — 서버에 한 번 박아두면 그 서버에서 뜨는 모든 프로세스가 자동으로 그 값을 갖는다. (dev·prod 가 다른 서버로 나뉘어 있을 때 쓸 수 있는 방식)
# [production 서버] /etc/environment 에 한 줄 추가
{프로젝트명대문자}_PRODUCTION=True
# [dev 서버] /etc/environment 에는 이 줄이 없다 → 그래서 자동으로 local 로 뜸
/etc/environment는export없이KEY=VALUE형식이다. 수정 후 새 셸/재로그인부터 적용된다.
방법 ② 명령줄(command line)에서 주입하기
프로세스를 띄우는 명령 앞에 환경변수를 붙여 그 프로세스에만 넣는다. OS 전역에 박지 않으므로 일회성 실행·테스트나, 같은 서버에서 프로세스마다 다른 값을 주고 싶을 때 쓴다.
# 그 명령 한 번에만 적용 (이 프로세스 한정)
{프로젝트명대문자}_PRODUCTION=True gunicorn {프로젝트명}.wsgi:application
# 또는 현재 셸 세션에 export 한 뒤 실행 (그 셸에서 띄우는 프로세스에 적용)
export {프로젝트명대문자}_PRODUCTION=True
python manage.py runserver
방법 ③ .env 파일에 적어두기
코드 옆 .env 파일에 KEY=VALUE 로 적고 python-dotenv 의 load_dotenv() 로 읽어 os.environ 에 채운다(§5·§6). 파일이라 .gitignore 로 빼서 비밀값(API 키·비밀번호) 을 담기 좋다.
# .env (git 에 안 올린다 — .gitignore)
SECRET_KEY=dev-secret-...
DB_PASSWORD=...
SENDGRID_API_KEY=...
# settings/__init__.py — .env 를 읽어 os.environ 에 채운 뒤 settings 가 그 값을 씀
from dotenv import load_dotenv
load_dotenv(_BASE_DIR / '.env') # 개발
# load_dotenv(_BASE_DIR / '.env.production') # 운영 (PRODUCTION 분기일 때)
그래서 우리 프로젝트는 실제로 어떻게 하나 — ② + ③
위 세 가지 중 우리 프로젝트(popax, popup_home_renewal 등)가 실제로 쓰는 조합은 ② 로 스위치 + ③ 으로 값이다. ①(/etc/environment)은 쓰지 않는다.
- 스위치는 systemd 서비스 유닛(리눅스가 프로그램을 자동으로 띄우고 관리하는 장치)이 박는다 (②의 실전판 — 명령줄 대신 서비스가 주입). prod 로 돌리는 gunicorn/huey 서비스에:
ini # /etc/systemd/system/popax-prod.service Environment="POPAX_PRODUCTION=True" ExecStart=/home/ubuntu/popax/bin/gunicorn popax.wsgi:application --bind 127.0.0.1:8024 ... - 값은
.env/.env.production두 파일로 나눠 담는다 (③). 스위치가 고른 환경의 파일을load_dotenv가 읽는다.
💡 왜 ①(
/etc/environment)이 아니라 ②(systemd)인가? popax 는 dev 와 prod 가 같은 서버(dev 박스)에서 함께 돈다. 서버가 하나면 OS 전역 파일(/etc/environment)로는 두 환경을 가를 수 없다 — 그래서 프로세스(서비스) 단위로 주입하는 ② 를 쓴다. (/etc/environment에는POPAX_PRODUCTION이 없다.)
- 결과적으로 코드는 dev/prod 양쪽에 똑같은 한 벌이 올라가고, 갈림은 프로세스를 띄울 때 그 환경변수가 박혀 있느냐가 만든다 — dev 의
runserver에는 없어서local, prod 의 systemd 서비스엔{프로젝트명대문자}_PRODUCTION=True가 박혀 있어서production으로 뜬다. - 결과적으로 settings 의 구성은:
- 개발:
base.py의 모든 내용 +local.py의 모든 내용 - 운영:
base.py의 모든 내용 +production.py의 모든 내용
9. base.py 에 들어가는 "공통" 설정
두 환경이 똑같이 쓰는 것은 전부 base.py 에 한 번만 넣는다. 강의에서 짚은 항목:
- Django 기본 앱:
admin,auth,sessions,messages,staticfiles - 설치한 라이브러리:
rest_framework(DRF),django_extensions등 - 미들웨어: CSRF, security, 요청/응답·HTTP 세션 관리 등
ROOT_URLCONF(루트 URL), 템플릿 엔진, WSGI 애플리케이션- 패스워드 검증기,
LANGUAGE_CODE,TIME_ZONE,USE_TZ - static / media 파일 저장 위치, 사용할 User 모델
- SendGrid API 키, Google OAuth client id, 로깅 설정
10. local 과 production 은 실제로 뭐가 다른가
반대로 환경마다 달라져야 하는 것만 local.py / production.py 로 갈린다.
| 항목 | local.py (개발) |
production.py (운영) |
|---|---|---|
SECRET_KEY |
개발용 | 운영용 (달라야 함 — 지금 같은 건 나중에 수정 예정) |
DEBUG |
True (에러가 나면 메시지를 브라우저에 표시) |
False (운영에선 에러 메시지를 띄우면 안 됨) |
ALLOWED_HOSTS |
localhost + dev 서버 IP 43.200.38.93 + dev 도메인 |
운영 도메인 |
CSRF |
dev 주소 기준 | 운영 주소 기준 (주소에 따라 체크가 달라짐) |
DATABASES |
dev 서버의 PostgreSQL (포트 5432) |
Production 서버의 PostgreSQL |
Django 앱은 자기를 서빙하는 IP/도메인으로 들어오는 요청만 처리한다. 그래서
ALLOWED_HOSTS에 그 주소들이 들어가 있어야 한다.
오늘 정리 + 다음
- 정리: 요청이 들어오면 Django 는 (앞단의 WSGI, 즉 웹서버와 Django 를 잇는 다리를 지난 다음에) URL 을 처리하는데, 그 전에 settings 가 조립된다. settings 에서 공통이 아닌 — 환경별로 갈려야 하는 값(DB · 도메인 · IP 등) — 을
local/production으로 분리해두고, 처음 settings 가 로드될 때 환경변수(PRODUCTION 여부)에 따라 둘 중 하나를 불러온다. - 개발 서버에서 개발할 때:
base + local - 사용자가 운영 URL 로 들어오면:
base + production - 흔한 함정: "분명히 저장했는데 DB에 없다" → 십중팔구 로컬 DB에 저장됐는데 운영 DB를 보고 있는 것. (요즘은 거의 없는 일이긴 하다.)
- 다음 시간: settings 관련해서 좀 더 디테일하게 설명할 예정.
- 자습 권장: 시간 날 때 Django settings 원리를 직접 한번 찾아보기.