L04 — Django static / media 파일과 settings (수정중)
settings 의 STATICFILES_DIRS / STATIC_URL / STATIC_ROOT 세 값이 각각 무슨 뜻인지 구분하고, 템플릿에서 {% static %} 로 CSS·JS 를 불러오며, dev 에선 Django 가 / prod 에선 Apache 가 static 을 서빙하는 이유(collectstatic 포함)를 설명할 수 있다.1. settings 의 또 다른 한 덩어리 — static
L01 에서 settings 가 base / local / production 으로 갈린다고 했고, L02 에선 그중 DATABASES 를 봤다. 오늘은 또 다른 한 덩어리 — static(정적) 파일 설정이다.
base.py 의 이 부분이다 (dev/prod 공통이라 base.py 에 있다 — L01 §9):
# popax/settings/base.py:101
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), "popax_static_cdn", "static")
MEDIA_URL = 'media/'
MEDIA_ROOT = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), "popax_static_cdn", "media")
다섯 줄밖에 안 되지만, "CSS·JS·이미지를 어디에 두고 / 어떤 URL 로 내보내고 / 배포할 땐 어디로 모으나" 가 다 들어 있다.
2. static 파일이 뭔가 — "코드와 함께 배포되는 자산"
static(정적) 파일 = 요청마다 바뀌지 않는, 우리가 직접 만들어 코드와 함께 올리는 파일.
- CSS(스타일), JS(동작), 이미지/폰트/아이콘 등이 전부 static 이다.
- "정적(static)" 인 이유: DB 값처럼 매 요청마다 계산해서 달라지는 게 아니라, 개발자가 미리 만들어 둔 그대로 브라우저에 내려가기 때문.
- popax 의 실제 static 폴더(
popax/src/static/):
static/
├── css/
│ ├── base.css ← 팀 공통 스타일 (standarda-template 에서 같이 옴)
│ ├── style.css
│ ├── meetings/ meeting_chat/ accounts/ ← 앱별 CSS
│ └── ...
└── js/
├── _memory_common.js
├── meetings/ meeting_chat/ accounts/ ← 앱별 JS
└── ...
static/css/base.css는 팀 공통 스타일이라standarda-template으로 관리된다(글로벌 규칙: 공통 파일 수정 시standarda-template반영). 프로젝트별 스타일은 그 위에style.css·앱별 CSS 로 얹는다.
3. 템플릿에서 static 부르기 — {% load static %} + {% static %}
HTML 에서 CSS/JS 를 불러올 때, 경로를 직접 /static/css/base.css 라고 박지 않는다. Django 의 {% static %} 태그를 쓴다. popax 의 실제 템플릿:
<!-- popax/src/templates/organizations/settings.html:1 -->
{% load static %} {# 파일 맨 위에서 한 번 로드 #}
...
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'css/base.css' %}">
<link rel="stylesheet" href="{% static 'css/meetings/meetings.css' %}">
...
<script src="{% static 'js/_memory_common.js' %}"></script>
<script src="{% static 'js/organization_settings.js' %}"></script>
{% static 'css/base.css' %}→ Django 가STATIC_URL을 앞에 붙여/static/css/base.css로 바꿔 렌더한다.- 왜 직접 박지 않나? static 을 내보내는 위치(
STATIC_URL)는 나중에 바뀔 수 있다. 예를 들어 CDN(파일을 따로 모아두는 외부 저장소,https://cdn.example.com/같은 주소)으로 옮길 수도 있다. 그때도 템플릿은 한 글자도 안 고친다. settings 의STATIC_URL한 줄만 바꾸면 된다. 경로를 한 곳(settings)에서 관리하는 것이다.
4. static 설정 3종 — 헷갈리기 쉬운 핵심
base.py 의 세 값은 이름이 비슷해서 자주 헷갈린다. 역할이 완전히 다르다.
| 설정 | popax 값 | 한마디로 | 누가 보나 |
|---|---|---|---|
STATICFILES_DIRS |
(BASE_DIR/"static",) |
내가 CSS·JS 를 써넣는 곳 (소스) | 개발자 |
STATIC_URL |
'static/' |
static 을 내보내는 URL 접두사 | 브라우저 |
STATIC_ROOT |
…/popax_static_cdn/static |
배포 때 싹 모아두는 곳 (출력) | 웹 서버(Apache) |
STATICFILES_DIRS(입력/소스): 내가 손으로 만드는popax/src/static/.BASE_DIR는src/(L01 §5) 이므로BASE_DIR/static=popax/src/static. 여기를 편집한다.STATIC_URL(URL): §3 의{% static %}가 앞에 붙이는 접두사(주소 앞머리)다.static/→ 브라우저는/static/...로 요청한다.STATIC_ROOT(출력/모음):collectstatic(§7) 이 모든 static 을 한 폴더로 복사해 두는 곳. 경로를 풀면os.path.dirname(os.path.dirname(BASE_DIR))=src/의 두 단계 위 =/home/ubuntu→/home/ubuntu/popax_static_cdn/static. 여기는 자동 생성물이라 직접 편집하지 않는다 (편집해도 다음collectstatic때 덮어써짐).
⚠️ 가장 흔한 혼동:
STATICFILES_DIRS(내가 쓰는 곳)와STATIC_ROOT(모아지는 곳)를 헷갈리는 것. 두 곳을 같은 폴더로 지정하면 안 된다 — Django 가 "소스와 모음 위치가 겹친다"며 에러를 낸다. 그래서STATIC_ROOT는 일부러src/바깥(/home/ubuntu/popax_static_cdn/)에 둔다.
5. static vs media — 비슷해 보이지만 다르다
base.py 엔 MEDIA_URL / MEDIA_ROOT 도 같이 있다. static 과 짝처럼 보이지만 성격이 정반대다.
| static | media | |
|---|---|---|
| 누가 만드나 | 개발자가 만들어 코드와 함께 배포 | 사용자가 앱을 쓰는 도중(런타임)에 업로드 |
| 예 | CSS, JS, 로고 이미지 | 프로필 사진, 첨부파일, 업로드한 녹음 |
| git | 코드라서 커밋함 | 사용자 데이터라 보통 커밋 안 함 |
| 설정 | STATIC_URL / STATIC_ROOT |
MEDIA_URL / MEDIA_ROOT |
- 둘 다
popax_static_cdn/아래에 나란히 모인다 (…/static,…/media) — 실제로 그 폴더엔static/과media/두 개가 있다. - 핵심 구분: static 은 "우리가 넣는 것", media 는 "사용자가 넣는 것". 그래서 media 는 모델의
FileField/ImageField로 업로드되어MEDIA_ROOT에 저장된다.
6. dev 에선 누가 static 을 서빙하나 — Django 가 직접
개발 서버(popax-dev.popupstudio.ai)에서 base.css 를 어떻게 받아오나? Django 가 직접 준다.
local.py는DEBUG = True(L01 §10).DEBUG=True일 때만 Django 의staticfiles앱이STATICFILES_DIRS(+각 앱의 static)를 뒤져서 요청을 자동으로 처리해준다.- 그래서 dev 의 Apache 설정(
/etc/apache2/sites-enabled/popax-dev.conf)은 static 을 따로 신경 쓰지 않고 전부 Django 로 넘긴다(이렇게 요청을 대신 받아 넘겨주는 걸 프록시라 한다):
# popax-dev (dev) — 전부 Django(:8023)로 프록시. static 구분 없음.
ProxyPass / http://127.0.0.1:8023/
ProxyPassReverse / http://127.0.0.1:8023/
→ dev 에선 collectstatic 도 필요 없다. 코드 고치고 새로고침하면 바로 반영된다.
7. prod 에선 — Django 가 안 준다 → collectstatic + Apache
Production(meetings.popupstudio.ai)은 production.py 가 DEBUG = False(L01 §10). DEBUG=False 면 Django 는 static 을 서빙하지 않는다. (보안·성능상 일부러 그렇게 설계돼 있다 — 웹 서버가 파일 서빙은 훨씬 잘한다.)
그래서 prod 는 두 가지가 필요하다:
① collectstatic — 흩어진 static 을 한 곳(STATIC_ROOT)에 모은다
python manage.py collectstatic
- 내
static/(STATICFILES_DIRS) + Django admin·DRF·django_extensions 같은 설치된 앱들의 static 을 전부 긁어STATIC_ROOT(/home/ubuntu/popax_static_cdn/static)로 복사한다. - 실제 popax 의 결과물:
$ ls /home/ubuntu/popax_static_cdn/static
admin css django_extensions js rest_framework # 총 186개 파일
→ css·js 는 내가 쓴 것, admin·rest_framework·django_extensions 는 라이브러리들이 들고 온 static. 한 폴더에 다 모였다.
② Apache 가 그 폴더를 직접 서빙 — /static/ 요청은 Django 로 안 보낸다
prod 의 Apache 설정 파일(vhost 라고 부른다, /etc/apache2/sites-enabled/popax-prod.conf)의 핵심:
# meetings.popupstudio.ai (prod)
Alias /static/ /home/ubuntu/popax_static_cdn/static/ # /static/ URL → 그 폴더에서 직접 읽기
Alias /media/ /home/ubuntu/popax_static_cdn/media/
<Directory /home/ubuntu/popax_static_cdn/static>
Require all granted
</Directory>
ProxyPass /static/ ! # ★ /static/ 은 Django(:8024)로 넘기지 말 것
ProxyPass /media/ !
ProxyPass / http://127.0.0.1:8024/ # 나머지(앱 로직)만 Django 로
Alias= "이 URL 로 오면 이 디스크 폴더에서 파일을 직접 꺼내줘라".ProxyPass /static/ !의!가 포인트다 — "/static/은 프록시(Django) 예외" 라는 뜻. 그래서 CSS/JS 요청은 Django 까지 안 가고 Apache 가 디스크에서 바로 응답한다. 앱 페이지(/meetings/...)만 Django 로 간다.
정리하면 prod 의 static 흐름: 브라우저가
/static/css/base.css요청 → Apache 가Alias로popax_static_cdn/static/css/base.css를 직접 읽어 응답 (Django 안 거침). 그래서 배포할 때마다collectstatic을 돌려 그 폴더를 최신으로 갱신해야 새 CSS 가 반영된다.
8. dev ↔ prod static 한눈에
L01 §10(설정 분리), L02 §6(DB 분리)과 같은 결의 비교. static 도 dev 는 Django, prod 는 Apache 로 갈린다.
dev (popax-dev, :8023) |
prod (meetings, :8024) |
|
|---|---|---|
DEBUG |
True |
False |
| static 서빙 주체 | Django(staticfiles)가 직접 |
Apache 가 Alias 로 직접 |
collectstatic |
불필요 | 배포 때마다 필요 |
| Apache 설정 | 전부 Django 로 프록시 | /static/·/media/ 는 프록시 예외(!) |
| 보는 폴더 | STATICFILES_DIRS(src/static) |
STATIC_ROOT(popax_static_cdn/static) |
핵심은 하나의 코드(base.py)를 그대로 올려도, DEBUG 한 줄과 Apache vhost 가 dev/prod 서빙을 가른다는 것 — L01 의 "코드는 한 벌, 갈림은 환경" 원리가 static 에서도 똑같이 적용된다.
오늘 정리 + 다음
- 정리: static 은 우리가 만들어 코드와 함께 배포하는 CSS·JS·이미지다. settings 의 세 값이 역할이 다르다 —
STATICFILES_DIRS(내가 쓰는 곳) →STATIC_URL(브라우저가 받는 URL) →STATIC_ROOT(배포 때 모으는 곳). 템플릿에선{% static %}로 부른다. dev 는DEBUG=True라 Django 가 직접 서빙하지만, prod 는DEBUG=False라collectstatic으로STATIC_ROOT에 모으고 Apache 가 직접 서빙한다. media 는 static 과 달리 사용자가 업로드하는 파일이다. - 흔한 함정: "prod 에 새 CSS 를 올렸는데 안 바뀐다" → 십중팔구
collectstatic을 안 돌린 것. prod 는src/static이 아니라popax_static_cdn/static을 보기 때문에, 거기로 복사(collectstatic)하기 전엔 옛날 파일이 그대로 서빙된다. (그다음으로 흔한 건 브라우저 캐시.) - 다음 시간: 모델(모델 = 테이블) 정의 → 마이그레이션 → 실제 테이블 생성 흐름 (L02 에서 예고한 것).
- 자습 권장: 내 프로젝트에서
python manage.py collectstatic --dry-run으로 어떤 파일이 어디로 모일지 미리 출력만 해보기(실제 복사는 안 함). 그리고{% static %}로 렌더된 페이지의 CSS 링크를 브라우저 개발자도구에서 확인해보기.