[Docker] WeasyPrint 시스템 의존성, Dockerfile 없는 구조에서 어떻게 설치하나
배경
발주서/견적서를 PDF로 변환하는 기능에 WeasyPrint를 쓰기로 했는데, 설치하다 보니 이 프로젝트 인프라의 특이한 구조부터 파악해야 했다.
이 프로젝트엔 Dockerfile이 없다
리포지토리 안을 아무리 찾아봐도 Dockerfile이나 docker-compose.yml이 없었다. 확인해보니 Synology Container Manager에서 공개 이미지를 그대로 pull해서 쓰는 구조였다. 대신 start_gunicorn.sh가 컨테이너 시작할 때마다 pip install -r requirements.txt를 실행해서, 그 시점에 파이썬 패키지를 설치하는 방식으로 운영되고 있었다.
문제 — WeasyPrint는 pip만으로 안 된다
weasyprint는 이미 requirements.txt에 들어있어서 파이썬 패키지 자체는 시작 스크립트로 설치될 예정이었다. 문제는 WeasyPrint가 필요로 하는 OS 레벨 라이브러리(Pango/Cairo/GDK-PixBuf)는 pip로 설치되지 않는다는 것 — apt-get으로 따로 설치해야 한다.
해결 — 운영(prod) 쪽은 시작 스크립트에 설치 단계 추가
Dockerfile 없이 시작 스크립트로 패키지를 처리하는 기존 패턴에 맞춰서, start_gunicorn.sh 맨 앞에 설치 단계를 추가했다.
#!/bin/bash
# Gunicorn 실행 스크립트
# WeasyPrint 시스템 의존성 설치 (Pango/Cairo/GDK-PixBuf) + 한글 폰트
if ! dpkg -s libpango-1.0-0 >/dev/null 2>&1; then
echo "🖨️ WeasyPrint 시스템 라이브러리 설치 중..."
apt-get update
apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangocairo-1.0-0 libcairo2 \
libgdk-pixbuf-2.0-0 libffi-dev shared-mime-info \
fonts-nanum
rm -rf /var/lib/apt/lists/*
fi
# Django가 설치되어 있는지 확인
...
포인트:
- dpkg -s libpango-1.0-0 체크로 이미 설치돼 있으면 건너뛰게 해서, 컨테이너가 재시작될 때마다 매번 다시 받지 않도록 함 (기존의 pip install 체크와 같은 패턴)
- fonts-nanum은 한글 폰트 — PDF에서 한글이 깨지지 않는지 확인하는 용도
- 컨테이너가 root로 실행 중이어야 apt-get install이 된다 — 안 되면 별도 권한 확인 필요
- 나중에 엑셀→PDF 변환 기능이 필요해지면 같은 자리에 LibreOffice 헤드리스(apt-get install -y libreoffice-calc)도 같은 방식으로 추가하면 된다
놓쳤던 부분 — 개발(dev) 컨테이너는 이 스크립트를 안 거친다
여기서 하나 놓친 게 있었다. start_gunicorn.sh에 넣은 설치 코드는 그 스크립트가 실행될 때만 적용된다. 그런데 개발 컨테이너는 pre-receive CI 훅에서 docker exec로 들어가 pytest/mypy를 돌리는 방식이지, push할 때마다 컨테이너를 새로 만들거나 start_gunicorn.sh를 재실행하는 구조가 아니었다. 컨테이너를 한 번 띄워놓고 runserver로 계속 살려두면서 쓰는 거라면 더더욱, 이 스크립트를 아예 거치지 않는다.
그래서 개발 컨테이너 쪽은 지금 떠 있는 컨테이너에 직접 한 번 설치해줘야 했다.
docker exec -it <dev-컨테이너명> bash
컨테이너 안에서:
apt-get update
apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangocairo-1.0-0 libcairo2 \
libgdk-pixbuf-2.0-0 libffi-dev shared-mime-info \
fonts-nanum
주의할 점 — 이 설치는 컨테이너가 사라지면 같이 사라진다
컨테이너 안에서 직접 설치한 패키지는, 나중에 그 컨테이너가 삭제되고 다시 만들어지면(이미지 자체는 안 바뀌었으니) 또 사라진다. start_gunicorn.sh에 넣은 것과 똑같은 한계다. 지금은 급한 대로 수동 설치로 뚫었지만, 컨테이너를 새로 만들 일이 생기면 그때 또 설치해줘야 한다는 걸 기억해둬야 한다 — 혹은 이 기회에 커스텀 Dockerfile로 옮기는 것도 고려해볼 만하다.
정리
- Dockerfile 없이 공개 이미지 + 시작 스크립트로 패키지를 설치하는 구조에서는, "이 스크립트를 실제로 거치는 컨테이너"와 "안 거치는 컨테이너"를 구분해야 한다
- prod처럼 매번 컨테이너가 새로 시작되는 쪽은 시작 스크립트에 설치 로직을 넣으면 되지만, dev처럼 한 번 띄워놓고 계속 쓰는 컨테이너는 그 스크립트를 거치지 않으므로 별도로 수동 설치가 필요하다
- 이런 임시방편이 반복된다면, 언젠가는 커스텀 Dockerfile로 전환해서 이미지 자체에 의존성을 박아두는 게 근본적인 해결책이다