Post

Docker 배포 정리

Docker 배포 정리

Django 프로젝트 Docker 배포 정리



1. 서버 접속하기

로컬 PC에서 서버에 SSH로 접속한다.

1
ssh 사용자id@접속IP


ex)

1
ssh ubuntu@123.123.123.123

접속하면 서버 안에서 프로젝트 폴더를 만들거나 기존 프로젝트 폴더로 이동해서 작업하면 된다.





2. 현재 서버에서 실행 중인 포트 확인하기

서버에서 이미 사용 중인 포트를 확인한다.

1
netstat -ntlp


또는 아래 명령어를 사용할 수도 있다.

1
sudo lsof -i -P -n | grep LISTEN


예를 들어 12345번 포트를 사용할 예정인데 이미 다른 프로그램이 사용 중이라면 Docker 실행 시 다른 포트를 사용해야 한다.

1
docker run -d -p 12345:8000 --env-file .env --name 컨테이너명 이미지명


포트 의미는 다음과 같다.

1
2
3
4
-p 12345:8000
   ↑     ↑
   │     컨테이너 내부 Django 서버 포트
   브라우저에서 접속할 포트

즉, 위처럼 실행했다면 브라우저에서는 아래 주소로 접속한다.

1
http://서버IP:12345





3. 프로젝트 폴더 구조 확인하기

Django 프로젝트는 보통 아래와 같은 구조가 되어야 한다.

1
2
3
4
5
6
7
8
project/
├── chat/
├── config/
├── source/
├── .env
├── Dockerfile
├── manage.py
└── requirements.txt

Dockerfile, manage.py, requirements.txt, .env 파일이 같은 프로젝트 폴더 안에 있어야 하고, Docker 이미지도 이 폴더 안에서 빌드해야 한다.


현재 위치는 pwd, 현재 폴더의 파일은 ls로 확인한다.

1
2
pwd
ls





4. requirements.txt 작성하기

Dockerfile에서 아래 명령어로 패키지를 설치하기 때문에 requirements.txt 파일이 필요하다.

1
RUN pip install --no-cache-dir -r requirements.txt


pyproject.toml을 사용하는 프로젝트라면 pyproject.toml의 dependencies 항목을 보고 requirements.txt에 작성하면 된다.


만약 로그에 아래와 같은 오류가 나온다면:

1
ModuleNotFoundError: No module named 'pymysql'

requirements.txt에 pymysql이 없거나, requirements.txt 수정 후 Docker 이미지를 다시 빌드하지 않은 것이다.

이 경우에는 이미지를 다시 빌드해야 한다. (16. 수정한 내용별 해야 할 작업 참고)





5. Dockerfile 만들기

프로젝트 폴더 안에 Dockerfile 파일을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM python:3.11-slim

# 컨테이너 내 작업 디렉토리 설정
WORKDIR /app

# requirements.txt 복사 후 패키지 설치
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt

# 프로젝트 전체 파일 복사
COPY . /app/

# Django 개발 서버 실행
# 컨테이너 내부에서는 8000번 포트로 실행
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]


각 줄의 의미는 다음과 같다.

  • FROM python:3.11-slim : Python 3.11이 설치된 가벼운 Linux 이미지를 기반으로 사용한다.
  • WORKDIR /app : 컨테이너 내부의 작업 위치를 /app으로 설정한다.
  • COPY requirements.txt + RUN pip install : requirements.txt를 컨테이너 안으로 복사한 뒤 필요한 Python 패키지를 설치한다.
  • COPY . /app/ : 현재 프로젝트 폴더의 모든 파일을 컨테이너 내부 /app으로 복사한다.
  • CMD [...] : 컨테이너가 실행되면 Django 서버를 실행한다.





6. settings.py 수정하기

Django는 기본적으로 허용된 호스트에서만 접속할 수 있다.

따라서 서버 IP를 ALLOWED_HOSTS에 추가해야 한다.

1
2
3
# config/settings.py

ALLOWED_HOSTS = ['서버접속IP', 'localhost', '127.0.0.1']


*운영 환경에서는 필요한 도메인 또는 서버 IP만 명시하는 것이 좋다.





7. MariaDB 연결용 .env 작성하기

Django 컨테이너에서 서버에 설치된 DB에 연결하려면 .env를 아래처럼 작성한다.

DB_NAME=project_db
DB_USER=user
DB_PASSWORD=비밀번호입력
DB_HOST=host.docker.internal
DB_PORT=3306


Docker 컨테이너에서 서버 DB에 연결할 것이기 때문에 DB_HOST=localhost가 아니라 DB_HOST=host.docker.internal로 작성했다.

내부에서 localhost는 MariaDB가 설치된 서버가 아니라 컨테이너 자기 자신을 의미한다.


그래서 Docker 실행 시 아래 옵션도 같이 넣어야 한다. (11. Docker 컨테이너 실행하기의 명령어에 포함되어 있다)

1
--add-host=host.docker.internal:host-gateway


비밀번호에 #이 들어가면 .env에서 주석처럼 처리될 수 있으므로 가능하면 피하는 것이 좋다. ex) #1234





8. MariaDB 데이터베이스와 사용자 생성하기

MariaDB에 root 계정으로 접속한다.

1
2
mysql -u root -p
# mysql -u root -p mysql


MariaDB 접속 후 아래 SQL을 실행한다.

1
2
3
4
5
6
7
8
9
CREATE DATABASE IF NOT EXISTS project_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER IF NOT EXISTS 'user'@'localhost' IDENTIFIED BY '비밀번호입력';
CREATE USER IF NOT EXISTS 'user'@'172.17.%' IDENTIFIED BY '비밀번호입력';

GRANT ALL PRIVILEGES ON project_db.* TO 'user'@'localhost';
GRANT ALL PRIVILEGES ON project_db.* TO 'user'@'172.17.%';

FLUSH PRIVILEGES;


각 SQL의 의미는 다음과 같다.

  • CREATE DATABASE : project_db라는 데이터베이스를 만든다. utf8mb4는 한글과 이모지 등을 안정적으로 저장하기 위한 문자셋이다.
  • 'user'@'localhost' : MariaDB 서버 내부에서 직접 접속하는 user 계정이다.
  • 'user'@'172.17.%' : Docker 컨테이너에서 접속하는 user 계정이다. Docker 기본 bridge 네트워크에서는 컨테이너 IP가 보통 172.17.0.2 같은 형태로 잡히기 때문에 172.17.%로 허용한다.
  • GRANT ALL PRIVILEGES : 각 계정에게 project_db 데이터베이스의 모든 테이블에 대한 권한을 준다.


아래처럼 정확한 컨테이너 IP만 허용하는 방식은 주의해야 한다.

1
'user'@'172.17.0.2'

컨테이너를 삭제하고 다시 만들면 IP가 바뀔 수 있기 때문에 Docker 대역(172.17.%)만 허용하는 방식이 더 낫다.


전체 허용인 'user'@'%'는 어떤 IP에서든 접속을 허용한다는 뜻이다.

개발 테스트에서는 편하지만, 배포 환경에서는 필요한 범위만 허용하는 것이 좋다.





9. MariaDB 사용자와 권한 확인하기

MariaDB에서 user 계정이 잘 만들어졌는지 확인한다.

1
SELECT user, host FROM mysql.user WHERE user = 'user';

정상이라면 아래처럼 나온다.

1
2
3
4
5
6
+------+-----------+
| User | Host      |
+------+-----------+
| user | 172.17.%  |
| user | localhost |
+------+-----------+



Docker 컨테이너 접속용 계정의 권한을 확인한다.

1
SHOW GRANTS FOR 'user'@'172.17.%';

정상이라면 아래처럼 나온다.

1
GRANT ALL PRIVILEGES ON `project_db`.* TO `user`@`172.17.%`


비밀번호를 변경해야 할 경우에는 아래처럼 실행한다.

1
2
3
4
ALTER USER 'user'@'localhost' IDENTIFIED BY '새비밀번호';
ALTER USER 'user'@'172.17.%' IDENTIFIED BY '새비밀번호';

FLUSH PRIVILEGES;

이 경우 .env의 DB_PASSWORD도 같은 값으로 수정해야 한다.





10. Docker 이미지 빌드하기

Docker 이미지는 프로젝트 폴더 안에서 빌드해야 한다.

1
docker build -t 이미지명 .


ex)

1
docker build -t my-django-app .

여기서 마지막의 .은 현재 폴더를 기준으로 Docker 이미지를 만들겠다는 의미이다.

따라서 반드시 Dockerfile, manage.py, requirements.txt가 있는 폴더에서 실행해야 한다.





11. Docker 컨테이너 실행하기

이미지를 빌드했다면 컨테이너를 실행한다.

이미 같은 이름의 컨테이너가 있으면 실행되지 않으므로 먼저 삭제하고 실행한다.

1
2
docker rm -f chat-container
docker run -d --name chat-container --add-host=host.docker.internal:host-gateway -p 12345:8000 --env-file .env -v "$(pwd):/app" my-django-app


Docker 이미지는 기본적으로 빌드 시점의 파일을 컨테이너 안에 복사하기 때문에, 코드를 수정하면 다시 빌드해야 반영된다.

위 명령어의 -v "$(pwd):/app" 옵션은 현재 프로젝트 폴더를 컨테이너 내부 /app과 연결한다.

이렇게 하면 파일 수정, 파일 추가, 파일 삭제가 컨테이너에 바로 반영되어 개발 중에 편하다.


볼륨 마운트 없이 실행하려면 -v 옵션만 빼면 된다.

1
docker run -d --name chat-container --add-host=host.docker.internal:host-gateway -p 12345:8000 --env-file .env my-django-app





12. 실행 확인과 로그 보기

컨테이너가 정상적으로 실행 중인지 확인한다.

1
docker ps

실행 중이라면 목록에 chat-container가 보이고, PORTS 부분에 아래처럼 보여야 한다.

1
0.0.0.0:12345->8000/tcp


만약 아무것도 나오지 않는다면 컨테이너가 실행되었다가 바로 꺼졌을 가능성이 있다.

이 경우 꺼진 컨테이너까지 전부 확인한다.

1
docker ps -a


컨테이너가 왜 꺼졌는지, Django 서버가 정상 실행되었는지는 로그로 확인한다.

1
2
3
docker logs chat-container            # 전체 로그
docker logs -f chat-container         # 실시간 로그
docker logs --tail 100 chat-container # 최근 100줄





13. Django 마이그레이션 실행하기

컨테이너가 실행 중이라면 Django migration을 실행한다.


makemigrations는 models.py 변경사항을 migration 파일로 만드는 단계이다.

1
docker exec -it chat-container python manage.py makemigrations

아래처럼 나오면 모델 변경사항이 없다는 뜻이다.

1
No changes detected



migrate는 migration 파일을 실제 DB에 적용하는 단계이다.

1
docker exec -it chat-container python manage.py migrate

아래처럼 나오면 DB 연결과 migration 상태가 정상이라는 뜻이다.

1
2
3
4
Operations to perform:
  Apply all migrations: admin, auth, chat, contenttypes, sessions
Running migrations:
  No migrations to apply.





14. Django 관리자 계정 만들기

관리자 계정은 Docker 컨테이너가 아니라 DB에 저장된다.

따라서 DB가 유지되면 컨테이너를 삭제하고 다시 만들어도 관리자 계정은 다시 만들 필요가 없다.


관리자 계정이 이미 있는지 확인하려면 Django shell에 들어간다.

1
docker exec -it chat-container python manage.py shell


그 다음 아래 코드를 실행한다.

1
2
from django.contrib.auth.models import User
User.objects.filter(is_superuser=True).exists()


결과가 True면 관리자 계정이 이미 있는 것이다.

False인 경우에만 아래 명령어로 관리자 계정을 만들면 된다.

1
docker exec -it chat-container python manage.py createsuperuser





15. 관리자 페이지 접속하기

관리자 계정을 만들었다면 브라우저에서 아래 주소로 접속한다.

1
http://서버IP:12345/admin/


ex)

1
http://123.123.123.123:12345/admin/


Django 관리자 페이지가 열리면 createsuperuser로 만든 아이디와 비밀번호로 로그인한다.





16. 수정한 내용별 해야 할 작업

수정한 내용해야 할 것
views.py, urls.py, chatbot_graph.py, 일반 Python 코드보통 자동 반영 또는 docker restart chat-container
templates/*.html, CSS, JS브라우저 새로고침, 안 되면 강력 새로고침
.env컨테이너 삭제 후 다시 실행
settings.pydocker restart chat-container 권장
models.pymakemigrations + migrate 필요 (13번)
requirements.txt, Dockerfiledocker build 다시 필요


코드 수정 후 재시작만 하고 싶을 때:

1
docker restart chat-container


.env를 수정했을 때:

.env 파일은 컨테이너를 만들 때 --env-file .env 옵션으로 한 번 읽힌다.

따라서 docker restart만으로는 부족할 수 있고, 컨테이너를 삭제하고 다시 실행하는 것이 확실하다.

1
2
docker rm -f chat-container
docker run -d --name chat-container --add-host=host.docker.internal:host-gateway -p 12345:8000 --env-file .env -v "$(pwd):/app" my-django-app


requirements.txt나 Dockerfile을 수정했을 때:

볼륨 마운트로 실행 중이어도 패키지 설치는 빌드할 때 일어나므로 이미지를 다시 빌드해야 한다.

1
2
3
docker rm -f chat-container
docker build -t my-django-app .
docker run -d --name chat-container --add-host=host.docker.internal:host-gateway -p 12345:8000 --env-file .env -v "$(pwd):/app" my-django-app





17. 오류 1: manage.py 파일을 찾을 수 없는 경우

Docker 실행 후 아래와 같은 오류가 나올 수 있다.

1
python: can't open file '/app/manage.py': [Errno 2] No such file or directory

이 오류는 컨테이너 내부의 /app 위치에 manage.py가 없다는 뜻이다.

대부분의 원인은 프로젝트 폴더가 아닌 홈 디렉토리에서 Docker 이미지를 빌드했기 때문이다.


pwd로 현재 위치를 확인했을 때 아래처럼 홈 디렉토리라면

1
/home/ubuntu


프로젝트 폴더로 이동한 뒤 다시 빌드한다.

1
2
cd project
docker build -t my-django-app .





18. 오류 2: 실행 중인 컨테이너는 삭제할 수 없는 경우

컨테이너를 삭제하려고 했을 때 아래와 같은 오류가 나올 수 있다.

1
cannot remove container : container is running: stop the container before removing or force remove

이 오류는 컨테이너가 아직 실행 중이기 때문에 바로 삭제할 수 없다는 뜻이다.


해결 방법 1: 정지 후 삭제

1
2
docker stop chat-container
docker rm chat-container


해결 방법 2: 강제 삭제

1
docker rm -f chat-container





19. 오류 3: 컨테이너 이름이 이미 사용 중인 경우

컨테이너 실행 시 아래 오류가 나올 수 있다.

1
Error response from daemon: Conflict. The container name "/chat-container" is already in use

같은 이름의 컨테이너가 이미 존재한다는 뜻이다. 꺼져 있어도 이름은 사용 중으로 처리된다.

docker rm -f chat-container로 기존 컨테이너를 삭제한 뒤 다시 실행한다. (11번 명령어)





20. 오류 4: DB 접속 권한 오류

로그에 아래 오류가 나오면:

1
django.db.utils.OperationalError: (1045, "Access denied for user 'user'@'172.17.0.2' (using password: YES)")

Docker 컨테이너에서 DB로 접속하는 계정 권한이 없거나, 비밀번호가 맞지 않는 것이다.


9번 방법으로 계정을 확인했을 때 user | 172.17.%가 없다면 추가한다.

1
2
3
CREATE USER IF NOT EXISTS 'user'@'172.17.%' IDENTIFIED BY '비밀번호입력';
GRANT ALL PRIVILEGES ON project_db.* TO 'user'@'172.17.%';
FLUSH PRIVILEGES;

계정이 있는데도 오류가 나면 .env의 DB_PASSWORD가 계정 비밀번호와 같은지 확인한다.





21. 전체 배포 흐름

처음 배포할 때는 아래 순서로 진행하면 된다.

(MariaDB 데이터베이스와 사용자는 8번에서 미리 만들어둔다)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 서버 접속
ssh ubuntu@123.123.123.123

# 2. 프로젝트 폴더로 이동 후 파일 확인
cd project
pwd
ls

# 3. 이미지 빌드
docker build -t my-django-app .

# 4. 컨테이너 실행
docker rm -f chat-container
docker run -d --name chat-container --add-host=host.docker.internal:host-gateway -p 12345:8000 --env-file .env -v "$(pwd):/app" my-django-app

# 5. 실행 확인 (꺼졌다면 docker ps -a)
docker ps
docker logs --tail 100 chat-container

# 6. DB 마이그레이션
docker exec -it chat-container python manage.py makemigrations
docker exec -it chat-container python manage.py migrate

# 7. 관리자 계정이 없을 때만
docker exec -it chat-container python manage.py createsuperuser


이후 브라우저에서 http://서버IP:12345로 접속하고, 관리자 페이지는 http://서버IP:12345/admin/으로 접속한다.

This post is licensed under CC BY 4.0 by the author.