📚 엔지니어 · 세션 · 프로젝트 이해 (수정중) · L03

L04 — Django static / media 파일과 settings (수정중)

작성일자 2026-06-17  ·  수정일자 2026-06-17  ·  강의차수 L03  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 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 링크를 브라우저 개발자도구에서 확인해보기.
이 강의를 학습하셨나요?