1. Ubuntu에서 Django Docker 이미지 만들기
작업 디렉토리:
cd ~/jw_project/testpro
1-1. Dockerfile 작성
대략 이런 형태로 구성:
FROM python:3.12-slim
WORKDIR /app
# 시스템 패키지 (mysqlclient 빌드용)
RUN apt-get update && apt-get install -y \
build-essential \
default-libmysqlclient-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["gunicorn", "mysite.wsgi:application", "--bind", "0.0.0.0:8000"]
1-2. 빌드 & 실행
# 이미지 빌드
sudo docker build -t jw_project:latest .
# 로컬 테스트 실행
sudo docker run --rm -p 8000:8000 --env-file .env jw_project:latest
gunicorn이 없어서 한 번 에러 났고 →requirements.txt에 추가 후 다시 빌드mysqlclient빌드 시pkg-config not found에러 →pkg-config,default-libmysqlclient-dev설치로 해결
curl http://127.0.0.1:8000 에서 HTML이 떨어지면,
컨테이너 안에서 Django + gunicorn 이 정상 동작하는 상태.
2. 이미지 파일로 뽑아서 Debian으로 옮기기
2-1. Docker 이미지를 tar로 저장
Ubuntu에서:
sudo docker save -o jw_project.tar jw_project:latest
ls -lh jw_project.tar # 용량 확인 (약 800MB 정도)
2-2. SSH 키로 Debian에 scp
Ubuntu → Debian 간 접속을 위해 별도 SSH 키 생성:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_debian -C "ubuntu-to-debian"
cat ~/.ssh/id_ed25519_debian.pub
이 공개키를 Debian 서버의 ~/.ssh/authorized_keys 에 등록 후, Ubuntu에서 테스트:
ssh -i ~/.ssh/id_ed25519_debian admin@13.209.77.43
정상 접속되면, tar와 .env 전송:
scp -i ~/.ssh/id_ed25519_debian jw_project.tar admin@13.209.77.43:~/
scp -i ~/.ssh/id_ed25519_debian .env admin@13.209.77.43:~/jw_project.env
2-3. Debian에서 Docker 설치 & 이미지 로드
Debian:
sudo apt update
sudo apt install docker.io -y
sudo systemctl enable --now docker
sudo docker load -i jw_project.tar
sudo docker images | grep jw_project
컨테이너 실행:
sudo docker run -d -p 8000:8000 --env-file jw_project.env --name jw_project jw_project:latest
sudo docker ps
AWS 보안 그룹에서 TCP 8000 포트 인바운드 허용 후,
http://13.209.77.43:8000 접속 → Django 기본 화면 확인.
3. DB를 Docker 컨테이너로 분리 (MariaDB 컨테이너 도입)
처음에는 Debian의 앱 컨테이너에서 Ubuntu의 MariaDB로 직접 붙어보려 했지만, 포트/보안그룹/권한 문제로 접속이 꼬여서 최종적으로는:
앱 컨테이너(jw_project) + DB 컨테이너(jw-mysql) 구조로 정리
3-1. Ubuntu에서 기존 DB 덤프 떠오기
Ubuntu (MariaDB):
sudo mysqldump quiz_db_team > quiz_db_team.sql
ls -lh quiz_db_team.sql
덤프 파일을 Debian으로 전송:
scp -i ~/.ssh/id_ed25519_debian quiz_db_team.sql admin@13.209.77.43:~/
3-2. Debian에서 Docker 네트워크 + MariaDB 컨테이너
Debian:
# 네트워크 생성
sudo docker network create jw-net
# MariaDB 컨테이너
sudo docker run -d \
--name jw-mysql \
--network jw-net \
-e MYSQL_ROOT_PASSWORD=Root_Quiz_2025! \
-e MYSQL_DATABASE=quiz_db_team \
-e MYSQL_USER=quiz \
-e MYSQL_PASSWORD=Strong_Quiz_2025! \
mariadb:11
덤프 파일 import:
sudo docker cp quiz_db_team.sql jw-mysql:/tmp/quiz_db_team.sql
sudo docker exec -i jw-mysql \
mariadb -u root -pRoot_Quiz_2025! quiz_db_team < quiz_db_team.sql
3-3. Django에서 DB 컨테이너를 바라보도록 설정
Debian의 jw_project.env:
DATABASE_NAME=quiz_db_team
DATABASE_USER=quiz
DATABASE_PASSWORD=Strong_Quiz_2025!
DATABASE_HOST=jw-mysql
DATABASE_PORT=3306
앱 컨테이너를 같은 네트워크에 붙여서 재실행:
sudo docker stop jw_project
sudo docker rm jw_project
sudo docker run -d \
--network jw-net \
-p 8000:8000 \
--env-file jw_project.env \
--name jw_project \
jw_project:latest
이제 앱 컨테이너는 컨테이너 이름 jw-mysql 로 DB에 접속한다.
(이게 “도커 네트워크 안에서의 서비스 이름 기반 통신”)
4. CSS 깨짐 / 정적 파일 404 문제 해결
4-1. 증상
- 페이지는 잘 뜨는데 CSS가 하나도 안 먹는다
-
DevTools Network 탭에서:
/static/quiz_app/styles.css→ Status 404-
그리고 MIME 타입 경고:
Refused to apply style from … because its MIME type (‘text/html’) is not a supported stylesheet MIME type…
= CSS 파일 URL로 요청했는데, HTML(404 페이지) 가 돌아오고 있다는 뜻.
4-2. 정적 파일 구조 확인
컨테이너 안:
sudo docker exec -it jw_project ls /app/static/quiz_app
# example_html.css, images, mp.css, munje_..., styles.css
그리고 collectstatic 이후:
sudo docker exec -it jw_project ls /app/staticfiles/quiz_app
# example_html.css, images, mp.css, munje_..., styles.css
파일 자체는 다 있다 → 문제는 “URL → 파일 매핑”.
4-3. settings.py 정리
mysite/settings.py:
STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
그리고 컨테이너 안에서 한 번 collectstatic:
sudo docker exec -it jw_project python manage.py collectstatic --noinput
4-4. urls.py 에서 static 매핑 추가
핵심은 이 부분.
mysite/urls.py:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("quiz_app.urls")),
path("accounts/", include("accounts.urls")),
path("accounts/", include("allauth.urls")),
path("history/", include("history.urls")),
path("community/", include("community.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
처음에는 MEDIA만 매핑되어 있고 STATIC 매핑이 빠져 있어서,
/static/... 요청이 전부 “일반 URL”로 흘러 들어가 404 HTML을 반환하고 있던 상태였다.
static 매핑 추가 후 컨테이너 재시작:
sudo docker restart jw_project
→ /static/quiz_app/styles.css 가 200 + text/css 로 떨어지고,
페이지 스타일 정상 복구
5. 오늘 배운 DevOps 포인트 정리
-
도커 이미지를 다른 서버로 옮기는 기본 패턴
docker build→docker save→scp→docker load→docker run- 애플리케이션 전체를 “이미지” 단위로 옮길 수 있어서 서버 갈아타는 부담이 줄어든다.
-
앱 컨테이너 / DB 컨테이너 분리
- Django와 DB를 분리해서 각각 컨테이너로 띄우고
-
도커 네트워크로 묶으면
- 이사(서버 이전)
- 스케일링(앱 컨테이너만 여러 개 늘리기) 가 훨씬 쉬워진다.
-
도커 내부에서의 “127.0.0.1” 의미
- 컨테이너 안의
127.0.0.1은 “호스트 서버”가 아니라 컨테이너 자기 자신 -
DB가 다른 서버/컨테이너에 있으면
- 서버 IP or 컨테이너 이름을
DATABASE_HOST로 써야 한다.
- 서버 IP or 컨테이너 이름을
- 컨테이너 안의
-
정적 파일은 코드 위치와 서비스 위치가 다르다
- 코드:
BASE_DIR/static/... - 서비스:
STATIC_ROOT(예:BASE_DIR/staticfiles) - 배포 모드에서는
collectstatic으로 모아놓고,urls.py+static()or Nginx로/static/을 이 위치로 매핑해야 한다.
- 코드:
-
문제가 생겼을 때 보는 순서
- 컨테이너 동작 여부:
docker ps,docker logs - 내부 통신 확인: 컨테이너 안에서
curl, DB 클라이언트로 직접 접근 -
정적 파일 깨짐:
- 파일이 실제로 있는지 (
static/staticfiles) STATIC_URL,STATIC_ROOT,STATICFILES_DIRSurls.py의static()매핑 또는 Nginx 설정
- 파일이 실제로 있는지 (
- 컨테이너 동작 여부:
마무리
오늘 작업 결과:
- Ubuntu → Debian으로 Django 프로젝트 완전 이사 완료
- 앱 컨테이너 + DB 컨테이너 구조로 정리
- CSS 깨짐까지 해결해서 “도커 기반 실전 배포 1세트” 경험 완료
이제 이 기반 위에:
- Nginx 리버스 프록시
- HTTPS (Let’s Encrypt / Cloudflare)
- Docker Compose / CI/CD
같은 것들을 하나씩 올려보면 될 것 같다 그리고 빌드 시간 단축하는 법도 연구해 보면 좋을 듯 하다