SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-10-13

VPS에 Paperless-ngx 설치 및 Docker Compose 설정

VPS 환경에서 Paperless-ngx를 Docker Compose로 구축하는 방법을 설명합니다. 공식 Postgres 스택 구성, PAPERLESS_URL 설정, OCR 언어 적용 및 데이터 백업 전략을 포함하여 문서 관리 시스템을 안정적으로 운영하기 위한 핵심 가이드를 제공합니다.

구축할 시스템

VPS에서 구동하는 Paperless-ngx는 스캔한 문서 폴더를 검색 가능한 아카이브로 변환합니다. 감시 대상 디렉터리에 PDF를 넣으면 서버가 OCR(광학 문자 인식)을 실행하여 텍스트를 추출하고, 날짜와 발신자를 추측하여 파일을 분류합니다. 설치는 4개의 서비스로 구성된 하나의 Docker Compose 파일로 이루어집니다. 그 이후의 모든 과정은 설정이며, 이 가이드의 대부분을 설정에 할애하는 이유는 설치가 실패하는 지점이 바로 그곳이기 때문입니다. 이 시스템은 사진 라이브러리가 아닙니다. OCR과 발신자 추측 기능은 휴가철 JPEG 폴더에는 아무런 도움이 되지 않으므로, 그러한 파일은 사진 전용 서버에 저장하고 Paperless는 문서용으로만 사용하십시오. 동영상도 마찬가지입니다. 리핑한 영화 파일은 미디어 서버에 두어야 하며, 90년대 대여점처럼 꾸민 Jellyfin 프론트엔드와 같은 도구를 사용하여 검색이 아닌 탐색의 즐거움을 누리는 것이 좋습니다.

Paperless-ngx는 원본 Paperless 프로젝트의 커뮤니티 유지 관리 포크 버전입니다. 무료이며 자체 호스팅이 가능하고 문서를 디스크에 일반 파일로 저장하므로, 자신의 아카이브에 접근하지 못하게 될 위험이 없습니다. 가정용 장비가 아닌 VPS에서 실행하면 홈 라우터의 포트를 개방하지 않고도 어디서나 스캔 파일에 접근할 수 있으며, 문서가 아닌 파일을 위한 개인용 Nextcloud 인스턴스와 함께 사용하기에도 좋습니다. 동일한 논리가 스캐너가 연결된 데스크톱에도 적용됩니다. VPS에 직접 구축한 RustDesk 릴레이를 사용하면 라우터에 구멍을 뚫지 않고도 외부에서 해당 장비를 원격으로 제어할 수 있습니다.

실제 실행되는 스택 구성

공식 compose 파일은 4개의 컨테이너를 시작하며, 각 컨테이너의 역할을 이해하면 로그를 쉽게 파악할 수 있습니다.

  • webserver: paperless-ngx 이미지 자체입니다. 웹 인터페이스, API, 입력 폴더를 감시하는 컨슈머, OCR을 수행하는 Celery 작업 워커를 실행합니다.
  • db: PostgreSQL입니다. 메타데이터, 태그, 발신자 정보, 전문 검색 인덱스 테이블을 저장합니다. PDF 파일 자체는 저장하지 않습니다.
  • broker: Valkey이며, Redis와 호환되는 키-값 저장소입니다. 웹 프로세스와 워커 사이의 작업 큐 역할을 합니다.
  • gotenberg 및 tika: 선택 사항이며, -tika compose 변형에서만 사용됩니다. Office 문서(.docx, .xlsx, .odt)를 PDF로 변환하여 paperless가 인덱싱할 수 있도록 합니다.

2026년 10월 기준으로 postgres compose 파일은 docker.io/library/postgres:18 및 docker.io/valkey/valkey:9-alpine 버전을 고정하며, 애플리케이션은 ghcr.io/paperless-ngx/paperless-ngx:latest에서 가져옵니다.

사전 요구 사항

  • sudo 권한이 있는 Ubuntu 24.04 KVM VPS와 Docker Compose 플러그인이 설치되어 있어야 합니다. 이 과정이 생소하다면 VPS를 위한 Docker Compose 기초를 먼저 확인한 뒤 돌아오십시오.
  • VPS를 가리키는 A 레코드가 설정된 도메인 이름이 필요합니다. Paperless는 사전에 지정되지 않은 호스트 이름으로 들어오는 HTTPS 로그인을 허용하지 않으므로, 이 설정은 예상보다 더 중요합니다.
  • 메모리가 가장 큰 제약 사항입니다. PostgreSQL, Valkey, Granian 웹 서버, Tesseract OCR 워커가 동시에 실행될 경우 가벼운 용도로는 2 GB 메모리로 충분합니다. 하지만 수백 건의 스캔 문서를 한꺼번에 가져올 계획이라면 4 GB를 할당하십시오. 대용량 다중 페이지 PDF의 OCR 작업 시 발생하는 메모리 급증으로 인해 커널의 OOM(Out-of-Memory) 킬러가 워커 프로세스를 종료시킬 수 있기 때문입니다.
  • 디스크: 원본 파일과 OCR 처리된 아카이브 PDF가 모두 저장되므로, 스캔 데이터 총 용량의 약 2배 정도를 여유 공간으로 확보하십시오.

공식 compose 파일 가져오기

대화형 설치 프로그램이 제공됩니다:

bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"

이 프로그램은 질문을 통해 사용자를 대신하여 파일을 작성합니다. 하지만 직접 수동으로 설정하면 4개의 명령어를 실행하는 과정에서 모든 파일의 위치를 파악할 수 있으며, 이는 서버를 유지보수해야 하는 관리자에게 권장되는 방식입니다.

mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.env

변형 파일들은 같은 디렉터리에 존재합니다: docker-compose.sqlite.yml, docker-compose.mariadb.yml, 그리고 각 파일의 -tika 버전이 있습니다. 신규 설치라면 postgres를 선택하십시오. SQLite는 수백 개의 문서를 다루기에는 적합하지만, 전체 텍스트 검색 인덱스 성능이 PostgreSQL보다 훨씬 빠르게 저하됩니다.

.env 파일에는 COMPOSE_PROJECT_NAME=paperless라는 한 줄의 내용이 들어 있습니다. 이 이름은 모든 컨테이너와 볼륨의 접두사(prefix)로 사용되므로, 파일을 삭제한 뒤 docker compose down -v에서 왜 데이터를 찾을 수 없는지 의문을 갖는 일이 없도록 주의하십시오.

첫 시작 전 docker-compose.env 설정하기

두 가지 설정은 선택 사항이 아닙니다. 프로젝트 문서에 명시된 명령어로 보안 키를 생성하십시오:

python3 -c "import secrets; print(secrets.token_urlsafe(64))"

그런 다음 docker-compose.env를 편집하십시오:

PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000

PAPERLESS_SECRET_KEY은 기본값으로 change-me이라는 문자열이 설정되어 있습니다. 이 값은 세션 토큰을 서명하는 데 사용되며, 최신 릴리스에서는 이 자리 표시자를 설정되지 않은 값으로 간주합니다. 키가 없거나, 비어 있거나, 여전히 change-me인 경우 paperless는 시작을 거부하므로, 이 값을 교체하기 전까지는 스택이 실행되지 않습니다. 나중에 변경하면 모든 사용자가 로그아웃되므로 첫 시작 전에 설정하십시오.

PAPERLESS_URL은 시간을 절약해 주는 설정입니다. Paperless는 Django 애플리케이션이며, Django는 Host 헤더와 로그인 폼에서 브라우저가 보고하는 출처(origin)를 모두 확인합니다. PAPERLESS_URL를 설정하면 해당 주소가 ALLOWED_HOSTS, CORS_ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS에 자동으로 추가됩니다. 이 값을 비워두고 도메인을 서버에 연결하면 로그인 페이지는 로드됩니다. PAPERLESS_ALLOWED_HOSTS의 기본값이 *이기 때문입니다. 하지만 HTTPS를 통해 제출하면 도메인이 신뢰할 수 있는 출처가 아니라는 이유로 CSRF verification failed. Request aborted. 오류가 발생하며 실패합니다. 마지막에 슬래시(/)나 경로를 포함하지 말고 작성하십시오.

USERMAP_UID와 USERMAP_GID은 컨테이너가 실행될 사용자를 지정합니다. id -u와 id -g로 확인한 본인의 계정 정보와 일치시켜야 합니다. 일치하지 않으면 consume 폴더에 복사한 파일을 컨슈머가 읽을 수 없으며, 로그에는 가져오기 대신 권한 오류가 표시됩니다.

스택 시작 및 첫 번째 사용자 생성

docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webserver

createsuperuser은 사용자 이름, 이메일, 비밀번호를 입력받습니다. 기본 로그인 계정은 없습니다. 이 단계를 건너뛰면 웹 인터페이스에 처음 접속하는 사람이 슈퍼유저를 생성하게 됩니다. 8000번 포트가 인터넷에 열려 있는 상태라면 본인이 아닐 수도 있으므로, 셸에서 즉시 계정을 생성하십시오. 브라우저로 접속하기 전에 서버가 8000번 포트에서 대기 중이라는 로그 메시지가 출력될 때까지 기다리십시오. 최초 실행 시에는 데이터베이스 마이그레이션이 수행되며, 이는 1~2분 정도 소요됩니다.

도메인을 연결하기 전에 로컬에서 확인하십시오:

curl -I http://127.0.0.1:8000

302이 /accounts/login/로 리다이렉트되면 스택이 정상적으로 작동하는 것입니다.

앞단에 HTTPS 배치하기

기본 제공되는 compose 파일은 8000:8000를 게시하며, 이는 모든 인터페이스에 바인딩됩니다. 공용 VPS에서 이 설정을 사용하면 주소를 알아낸 누구에게나 평문 HTTP로 전체 문서 아카이브가 노출됩니다. 포트 설정을 루프백에만 바인딩되도록 변경하십시오:

    ports:
      - "127.0.0.1:8000:8000"

그런 다음 리버스 프록시에서 TLS(transport layer security)를 종료하고 127.0.0.1:8000으로 전달하십시오. 서버에 이 애플리케이션만 있다면 ACME(automatic certificate management environment) 클라이언트를 지원하는 어떤 프록시든 사용할 수 있습니다. 여러 컨테이너를 하나의 인증서 설정 뒤에서 운영 중이라면 여러 Docker Compose 앱을 위한 Traefik 리버스 프록시 패턴을 따르고, webserver 서비스를 포트 게시 없이 프록시 네트워크에 연결하십시오.

어떤 프록시를 사용하든 로그인 폼의 오리진 검사 단계에서 HTTPS 설정 문제가 발생하기 쉽습니다. Paperless는 PAPERLESS_PROXY_SSL_HEADER을 설정하지 않으면 X-Forwarded-Proto를 무시합니다. 이 옵션은 기본적으로 꺼져 있으므로 Django는 모든 프록시 요청을 평문 HTTP로 간주하며, 브라우저가 전송하는 https:// 오리진은 신뢰할 수 있는 목록에 포함되어야만 통과됩니다. PAPERLESS_URL를 브라우저에 입력하는 정확한 https:// 주소로 설정해야 해당 목록에 추가됩니다. 이 설정이 없으면 페이지는 정상적으로 보여도 CSRF verification failed. Request aborted. 오류가 발생합니다.

또한 프록시의 업로드 크기 제한을 상향 조정하십시오. 프록시의 본문 크기 제한이 1 MB인데 40 MB짜리 스캔 파일을 업로드하면 Paperless에 도달하기도 전에 거부되며, 브라우저는 일반적인 업로드 실패 메시지를 표시합니다.

consume 디렉터리 작동 방식

compose 파일은 ./consume을 compose 디렉터리에서 컨테이너 내부로 바인드 마운트합니다. 해당 폴더에 넣은 모든 파일은 가져오기 처리된 후 폴더에서 삭제됩니다. 파일은 이제 paperless가 관리하는 media 볼륨으로 이동했기 때문입니다.

cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserver

consumer가 파일 이름을 인식하고, OCR을 실행한 뒤, 문서가 추가되었다는 보고 메시지를 출력하는 것을 확인할 수 있습니다. 한 페이지 분량의 스캔은 전체 과정이 몇 초 내에 끝나지만, 긴 문서는 1분 이상 걸릴 수 있습니다.

두 가지 설정으로 파일 탐색 방식을 변경할 수 있습니다. PAPERLESS_CONSUMER_RECURSIVE=true를 설정하면 paperless가 하위 폴더까지 탐색하며, PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true을 설정하면 각 하위 폴더 이름을 태그로 변환합니다. 따라서 파일을 consume/invoices/2026/에 넣으면 해당 파일에 invoices 및 2026 태그가 자동으로 지정됩니다. 이는 구축할 수 있는 가장 효율적인 파일 정리 시스템입니다.

파일 감지는 또 다른 핵심 요소입니다. 기본적으로 PAPERLESS_CONSUMER_POLLING_INTERVAL는 0로 설정되어 있으며, 이는 paperless가 즉각적으로 반응하는 네이티브 파일 시스템 알림을 사용함을 의미합니다. 하지만 이러한 알림은 네트워크 파일 시스템에서는 신뢰할 수 없습니다. 네트워크 스캐너가 파일을 쓸 수 있도록 consume 폴더를 NFS나 SMB 공유로 설정한 경우, 파일이 감지되지 않고 방치될 수 있습니다. 이 경우 PAPERLESS_CONSUMER_POLLING_INTERVAL를 양수(초 단위)로 설정하여 paperless가 주기적으로 폴더를 스캔하도록 변경해야 합니다.

OCR 언어 및 비용

PAPERLESS_OCR_LANGUAGE은 3글자 Tesseract 코드를 인자로 받으며, 기본값은 eng입니다. deu+eng과 같이 더하기 기호를 사용하여 언어를 조합할 수 있습니다. Tesseract는 나열된 모든 언어를 각 페이지마다 적용하므로, 언어를 추가할 때마다 모든 스캔 작업의 CPU 사용 시간이 늘어납니다. vCPU를 공유하는 VPS 환경에서는 스캔 완료 시간이 10초에서 1분으로 늘어나는 차이가 발생할 수 있습니다. 문서에 실제로 사용된 언어만 나열하십시오.

이미지에는 영어, 독일어, 이탈리아어, 스페인어, 프랑스어가 포함되어 있습니다. 그 외의 언어가 필요한 경우 PAPERLESS_OCR_LANGUAGES에 공백으로 구분된 목록 형태로 언어를 추가하고(예: PAPERLESS_OCR_LANGUAGES=tur ces) 재시작하십시오. 컨테이너는 시작 시점에 Tesseract 데이터 팩을 다운로드하므로, 설정을 변경한 후의 첫 번째 부팅은 평소보다 느립니다.

데이터베이스 및 미디어 백업

PostgreSQL이 실행 중인 상태에서 Docker 볼륨을 복사하면 복구 불가능한 백업이 생성될 수 있습니다. Paperless는 자체 내보내기 도구를 제공하며, 이 도구는 문서와 모든 메타데이터가 포함된 JSON 매니페스트를 ./export 바인드 마운트 경로에 기록합니다:

docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar

--delete 옵션은 현재 문서와 일치하지 않는 내보낸 파일을 제거하므로, 폴더가 무한정 커지지 않고 현재 상태를 유지합니다. --no-progress-bar 옵션은 cron에서 실행할 때 출력 내용을 깔끔하게 유지하며, -T 옵션은 cron에 터미널이 없어도 Compose가 터미널을 요구하지 않도록 합니다.

복구는 새로운 스택의 동일한 폴더를 대상으로 document_importer 명령을 실행하면 되며, 따라서 내보내기 디렉터리만 안전하게 보관하면 됩니다. VPS에서 암호화 및 중복 제거된 restic 백업을 사용하여 정기적으로 외부로 전송하십시오. 이때 restic이 작성 중인 아카이브를 캡처하지 않도록 내보내기 작업을 먼저 실행해야 합니다.

export/manifest.json 파일이 존재하는지, 그리고 파일 개수가 인터페이스상의 문서 개수와 일치하는지 확인하여 백업을 검증하십시오. 목록을 확인하지 않은 백업은 백업이 아닙니다. 조용히 실패하는 야간 내보내기 작업은 더 위험하므로, cron 작업의 종료 상태를 사용 중인 ntfy 서버로 전송하도록 설정하십시오. 그러면 복구가 필요한 날이 아니라 문제가 발생한 주에 즉시 알림을 받을 수 있습니다.

FAQ

도메인을 연결한 후 모든 페이지에서 "Bad Request (400)" 오류가 발생하는 이유는 무엇입니까?

기본 설정 파일에서는 발생하지 않아야 합니다. PAPERLESS_ALLOWED_HOSTS의 기본값이 *이기 때문입니다. 컨테이너 로그에 DisallowedHost과 함께 400가 나타난다면, PAPERLESS_ALLOWED_HOSTS에 설정된 목록에 사용자의 도메인이 포함되지 않았음을 의미합니다. docker-compose.env 파일에서 PAPERLESS_URL=https://paperless.example.com를 설정하십시오(끝에 슬래시를 붙이지 마십시오). 그러면 paperless가 해당 호스트 이름을 목록에 추가합니다. 또는 PAPERLESS_ALLOWED_HOSTS 줄을 삭제하십시오. 그 후 docker compose up -d를 실행하여 컨테이너를 다시 생성하십시오. 환경 변수 파일만 수정해서는 아무런 변화가 없습니다. 실행 중인 컨테이너는 시작 시점의 환경을 그대로 유지하기 때문입니다.

consume 폴더에 PDF를 넣었는데 아무 일도 일어나지 않습니다. 무엇이 문제입니까?

먼저 docker compose logs webserver을 확인하십시오. 권한 오류가 발생했다면 USERMAP_UID과 USERMAP_GID이 파일 소유자 계정과 일치하지 않는 것입니다. 이 값을 수정하고 컨테이너를 다시 생성하십시오. 로그에 아무런 기록도 남지 않는다면 파일 이벤트가 전달되지 않은 것입니다. 이는 네트워크 공유 환경에서 자주 발생하는데, 기본 파일 시스템 알림 기능이 네트워크 드라이브에서는 신뢰할 수 없기 때문입니다. PAPERLESS_CONSUMER_POLLING_INTERVAL를 30과 같이 설정하면 paperless가 30초마다 폴더를 스캔하게 됩니다.

paperless-ngx를 PostgreSQL 대신 SQLite로 실행할 수 있습니까?

네, docker-compose.sqlite.yml은 지원되며 메모리 사용량이 적어 소규모 VPS에 적합합니다. 다만 아카이브가 커질수록 성능 저하가 나타납니다. 문서가 수천 건 단위로 쌓이면 전체 텍스트 검색과 대량 태그 편집 속도가 눈에 띄게 느려집니다. 나중에 마이그레이션하려면 내보내기와 가져오기 과정이 필요하므로, 아카이브가 계속 커질 것으로 예상된다면 지금 PostgreSQL을 선택하십시오.

스캔한 문서 아카이브에는 어느 정도의 디스크 공간이 필요합니까?

원본 파일 크기의 대략 2배 정도가 필요합니다. paperless는 원본을 그대로 유지하면서 검색 가능한 텍스트 레이어가 포함된 OCR 처리된 PDF를 별도로 저장하며, 작은 썸네일 파일도 생성합니다. 200 KB 크기의 텍스트 전용 스캔본은 용량을 적게 차지하지만, 30 MB 크기의 컬러 계약서 스캔본은 약 60 MB를 차지합니다. 내보내기(export) 디렉터리까지 같은 디스크에 유지한다면, 동일한 아카이브가 디스크에 3번 저장되는 셈입니다.

Tika와 Gotenberg 컨테이너가 꼭 필요합니까?

PDF 외에 Word, Excel, OpenDocument 파일도 함께 인덱싱하고 싶을 때만 필요합니다. 이 컨테이너들은 해당 형식들을 PDF로 변환하여 paperless가 OCR을 수행하고 검색할 수 있게 합니다. 또한 실행 중인 컨테이너가 2개 더 늘어나고 수백 MB의 메모리를 추가로 점유하므로, 저장하는 모든 파일이 이미 PDF나 이미지라면 소규모 서버에서는 제외해도 좋습니다.

이 글이 도움이 되었나요?

#paperless-ngx#documents#self-hosting#docker#ocr