Mealie 셀프 호스팅: VPS에 레시피 관리자 설치하기
Docker Compose를 사용하여 VPS에 Mealie를 설치하는 방법을 설명합니다. 레시피 자동 추출부터 식단 계획, 쇼핑 목록 관리까지 가능합니다. 특히 Docker 포트 바인딩 시 주의해야 할 ufw 우회 문제와 안정적인 버전 고정 방법을 포함하여 실무적인 가이드를 제공합니다.
셀프 호스팅 레시피 관리자의 역할
셀프 호스팅 레시피 관리자는 사용자가 소유한 서버의 데이터베이스에 레시피를 저장하며, Mealie는 많은 가정에서 가장 선호하는 도구입니다. 레시피 페이지의 주소를 붙여넣으면 Mealie가 재료, 조리 단계, 분량, 조리 시간을 읽어오고, 불필요한 이야기나 광고는 제외합니다. 결과적으로 사용자의 컬렉션에는 오직 음식 정보만 남게 됩니다.
이 앱의 나머지 기능은 간결합니다. 레시피를 끌어다 놓을 수 있는 주간 식단 계획표가 있고, 이 계획을 바탕으로 쇼핑 목록이 생성됩니다. 요리를 하는 각 사용자는 개별 계정으로 로그인합니다. 모든 기능은 하나의 컨테이너에서 실행되며 요청이 없을 때는 유휴 상태를 유지하므로, 사양이 낮은 VPS에서도 무리 없이 운영할 수 있습니다.
이 가이드는 Docker Compose를 사용합니다. 만약 services: 및 volumes:이라는 용어가 생소하다면, 먼저 Docker Compose 파일 구성 방법을 읽어보시기 바랍니다. 아래의 모든 내용은 하나의 compose 파일과 네 개의 명령어로 이루어져 있기 때문입니다.
Docker Compose를 사용하여 Mealie 설치하기
Mealie는 GitHub 컨테이너 레지스트리에 이미지를 게시합니다. 2026년 7월 기준 최신 안정 버전 태그는 v3.22.0입니다. latest을 사용하는 대신 특정 버전을 고정하십시오. latest를 사용하면 관련 없는 날짜에 발생한 docker compose pull로 인해 준비되지 않은 데이터베이스 마이그레이션이 수행될 수 있습니다.
sudo mkdir -p /srv/mealie
cd /srv/mealie
sudo nano docker-compose.ymlservices:
mealie:
image: ghcr.io/mealie-recipes/mealie:v3.22.0
container_name: mealie
restart: always
ports:
- "127.0.0.1:9925:9000"
deploy:
resources:
limits:
memory: 1000M
volumes:
- mealie-data:/app/data/
environment:
ALLOW_SIGNUP: "false"
PUID: 1000
PGID: 1000
TZ: Europe/Amsterdam
BASE_URL: https://recipes.example.com
volumes:
mealie-data:시작하기 전에 두 줄을 주의 깊게 살펴보아야 합니다.
포트는 9925:9000이 아닌 127.0.0.1:9925:9000으로 작성되었습니다. 컨테이너는 내부적으로 9000번 포트에서 대기하며, 호스트는 9925번 포트를 이 포트에 매핑합니다. 이 매핑을 루프백 주소에 바인딩하면 Nginx는 Mealie에 접근할 수 있지만 외부 인터넷에서는 접근할 수 없습니다. Docker는 패킷 필터에 자체 규칙을 작성하므로, 방화벽에서 포트를 닫았더라도 일반적인 9925:9000은 외부에서 접근 가능합니다. 이러한 예기치 못한 상황을 한 번 이해해 두는 것이 좋습니다. Docker가 게시한 포트가 ufw를 무시하는 이유를 참조하십시오.
BASE_URL는 스킴을 포함하고 끝에 슬래시가 없는, 실제로 사용할 정확한 공용 주소여야 합니다. Mealie는 이 주소를 기반으로 비밀번호 재설정 링크와 초대 링크를 생성합니다. 이를 http://localhost:9925으로 설정하면 파트너에게 보낸 초대 링크가 서버 내부에서만 작동하게 됩니다.
서비스를 시작하고 첫 부팅 과정을 확인하십시오.
sudo docker compose up -d
sudo docker compose logs -f mealie첫 실행 시 SQLite 데이터베이스가 생성되고 마이그레이션이 수행되며, 이는 몇 초 정도 소요됩니다. 로그가 안정화되고 마이그레이션 관련 메시지 출력이 멈추면 로컬에서 앱을 확인하십시오.
curl -I http://127.0.0.1:9925200 OK은 앱이 정상적으로 실행 중임을 의미합니다. Connection refused는 컨테이너가 실행되고 있지 않음을 의미합니다. 이때는 sudo docker compose ps을 실행하여 종료 코드를 확인하십시오. 코드 137로 종료된 컨테이너는 1000M 메모리 제한을 초과하여 강제 종료된 것이며, 이는 가장 작은 사양의 플랜에서 발생할 수 있습니다.
최초 로그인 및 회원가입 기능 비활성화
기본 계정은 changeme@example.com이며 비밀번호는 MyPassword입니다. 해당 계정으로 로그인한 뒤 즉시 두 정보를 모두 변경하십시오. 해당 조합은 문서에 공개되어 있어 모든 스캐너가 탐색 대상으로 삼고 있습니다.
compose 파일의 ALLOW_SIGNUP: "false" 설정은 의도된 것입니다. 회원가입이 열려 있으면 주소를 알아낸 누구나 귀하의 레시피 보관함에 계정을 생성할 수 있습니다. 이를 닫으면 관리자 페이지에서 직접 사용자를 추가할 수 있으며, 이때 생성되는 초대 링크를 직접 전달하면 됩니다. 해당 링크는 BASE_URL을 기반으로 생성되므로 이 값은 중요합니다. 만약 동일한 서버에서 여러 애플리케이션을 운영하면서 하나의 비밀번호로 통합 관리하고 싶다면, Mealie의 로그인 기능을 직접 호스팅하는 Authentik 인스턴스와 같은 외부 ID 공급자에게 위임할 수 있습니다.
Mealie는 사용자를 가구 단위로 그룹화합니다. 한 가구에 속한 모든 사용자는 레시피 모음, 식단 계획, 쇼핑 목록을 공유하며, 이는 가족 단위 사용자에게 적합한 방식입니다. 동일한 서버 내에서 가구를 분리하면 각기 다른 모음을 유지할 수 있으므로, 앤초비 취향이 서로 다른 셰어하우스 환경에 적합합니다.
임포터: 이 도구를 실행하는 이유
레시피 모음집을 열고 URL에서 레시피를 생성하는 기능을 선택한 뒤 링크를 붙여넣으십시오. Mealie는 해당 페이지를 가져와 구조화된 레시피 데이터, 즉 대부분의 레시피 사이트가 검색 엔진을 위해 삽입하는 기계 판독 가능한 블록을 찾습니다. 해당 블록이 존재하면 가져오기 과정이 깔끔하고 즉각적으로 완료됩니다.
이미지나 직접 붙여넣은 일반 텍스트로도 가져올 수 있으며, 이는 요리책 페이지를 촬영한 사진 등을 처리할 때 유용합니다. 이 방식은 더 느린 경로를 거치며, 손글씨로 적힌 분수 등은 오독하기 쉬우므로 가져온 후 확인이 필요합니다.
대량 가져오기 역시 같은 화면에서 실행할 수 있습니다. 주소 목록을 한 줄에 하나씩 붙여넣으면 Mealie가 백그라운드에서 작업을 수행합니다. 200개의 북마크 모음도 한 번에 옮길 수 있습니다.
식단 계획 및 쇼핑 목록
식단 플래너는 달력 형태입니다. 레시피를 특정 날짜로 드래그하면 계획이 완료됩니다. 쇼핑 목록은 계획된 레시피의 재료를 하나의 목록으로 수집하며, 중복 항목을 합칩니다. 예를 들어 두 레시피에 모두 양파가 필요하면 두 줄이 아닌 한 줄로 표시됩니다.
이 목록은 매장에서 휴대폰으로 바로 확인할 수 있는 실시간 페이지입니다. 사용자의 서버가 직접 목록을 관리하므로, 가구 구성원 모두가 동시에 동일한 목록을 볼 수 있습니다. 한 사람이 우유 항목을 체크하면 다른 사람의 화면에서도 해당 항목이 즉시 사라집니다.
Nginx와 TLS를 앞단에 배치하기
Mealie는 일반 HTTP로만 통신하며 자체적인 인증서 처리 기능이 없습니다. 앞단에 Nginx를 두어 TLS(Transport Layer Security)를 종료하십시오. 인증서 발급 단계에서 도메인 이름을 검증하므로, 먼저 서버를 가리키는 DNS A 레코드를 설정해야 합니다.
sudo apt update && sudo apt install -y nginx
sudo nano /etc/nginx/sites-available/mealieserver {
listen 80;
server_name recipes.example.com;
client_max_body_size 64M;
location / {
proxy_pass http://127.0.0.1:9925;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo ln -s /etc/nginx/sites-available/mealie /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxnginx -t를 출력하고 syntax is ok와 test is successful을 확인하는 것이 관문입니다. 설정 파일에 오류가 있는 상태에서 리로드하면 기존 설정이 그대로 유지되어 다음 재시작 전까지 실수를 알아차리기 어렵기 때문에, 반드시 검증을 통과한 뒤에 리로드하십시오.
client_max_body_size 64M을 설정하는 이유는 Nginx의 기본값이 1 MB이기 때문입니다. 브라우저를 통해 레시피 사진을 업로드하거나 백업을 복원할 때 이보다 큰 데이터를 전송하게 되는데, 이 설정이 없으면 Mealie가 아닌 Nginx로부터 413 Request Entity Too Large 오류를 받게 되어 애플리케이션 로그에는 아무런 기록도 남지 않습니다.
그다음 인증서를 발급하십시오. 해당 단계와 갱신 타이머 설정은 Certbot을 사용하여 Nginx용 Let's Encrypt 인증서 발급하기에서 다룹니다.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d recipes.example.comCertbot은 서버 블록을 다시 작성하여 443 포트에서 수신하도록 만들고 80 포트에서 리다이렉트 설정을 추가합니다. https://을 통해 사이트에 접속하여 브라우저가 인증서를 신뢰하는지 확인하십시오. Mealie는 로드되지만 내부 링크가 http://로 연결된다면, BASE_URL의 값이 여전히 http으로 설정되어 있을 가능성이 큽니다. 이 경우 값을 수정한 뒤 sudo docker compose up -d을 실행하여 컨테이너를 다시 생성해야 합니다.
Mealie를 example.com/recipes과 같은 하위 경로에서 서비스하는 것은 불가능합니다. 프론트엔드가 하위 경로에서 동작하도록 설계되지 않았기 때문입니다. 서브도메인을 사용하십시오.
백업과 복구의 실제 동작 방식
Mealie가 보유한 모든 데이터는 컨테이너 내부의 /app/data/에 위치하며, 이는 mealie-data 볼륨입니다. 이 볼륨을 복사하면 레시피, 이미지, 데이터베이스를 한꺼번에 복사하는 셈이 됩니다.
sudo docker volume ls
sudo docker compose stop mealie
sudo docker run --rm -v mealie_mealie-data:/data -v "$PWD":/backup \
alpine tar czf /backup/mealie-data.tgz -C /data .
sudo docker compose start mealie볼륨 이름에는 프로젝트 이름이 접두사로 붙으며, 프로젝트 이름은 compose 파일을 담고 있는 디렉터리 이름입니다. /srv/mealie에서 볼륨 이름은 mealie_mealie-data이므로, 첫 번째 명령어로 docker volume ls을 사용하는 것입니다. 이 가이드에 적힌 이름이 아닌, 명령어가 출력하는 실제 이름을 사용하십시오. SQLite는 쓰기 작업이 빈번하게 발생하므로, 라이브 상태에서 복사하면 복구 시 읽을 수 없는 파일이 될 가능성이 큽니다. 따라서 컨테이너를 먼저 중지하는 것이 중요합니다.
Mealie 관리자 페이지에는 자체 백업 기능이 있습니다. 이 기능은 데이터베이스를 JSON 형식으로 변환하고 이미지와 함께 휴대 가능한 아카이브로 저장합니다. 서버를 이전할 때는 이 방식을 사용하십시오. 원본 파일을 그대로 복사하는 방식과 달리 버전 변경 시에도 데이터가 유지되기 때문입니다. 복구 작업은 설계상 파괴적입니다. 아카이브를 불러오기 전에 현재 데이터베이스를 삭제하며, 이 작업은 되돌릴 수 없습니다. 작업이 완료되면 자동으로 로그아웃됩니다.
동일한 서버에 저장된 복사본은 진정한 의미의 백업이 아닙니다. restic을 이용한 암호화된 외부 백업에서 설명하는 것처럼, 일정에 따라 아카이브를 다른 곳으로 전송하십시오.
Mealie 업데이트
cd /srv/mealie
sudo nano docker-compose.yml
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs -f mealie파일에 고정된 버전을 올린 뒤, 이미지를 pull하고 컨테이너를 다시 생성합니다. 새 이미지를 처음 시작할 때 마이그레이션이 자동으로 실행됩니다. 메이저 버전 변경 전에는 반드시 볼륨을 백업하십시오. 마이그레이션이 도중에 실패하면 이전 버전의 이미지로는 데이터베이스를 더 이상 열 수 없게 됩니다. 현재 버전과 새 버전 사이에 포함된 모든 릴리스 노트를 확인하십시오.
임포터가 실패할 때
일부 사이트는 구조화된 레시피 데이터를 전혀 게시하지 않으며, 이 경우 Mealie는 빈 재료 목록과 함께 제목만 가져옵니다. 이는 설정으로 해결할 수 있는 문제가 아닙니다. 대신 레시피 텍스트를 직접 붙여넣으십시오.
다른 실패 사례는 레시피 사이트 앞단에 있는 봇 차단 기능 때문입니다. 이 기능은 Mealie에 레시피 대신 챌린지 페이지를 응답합니다. Mealie는 이미 브라우저를 가장하고 User Agent를 순환시켜 이를 완화하고 있습니다. 그럼에도 사이트가 거부한다면, 문서화된 해결 방법은 더 나은 주소 평판을 가진 프록시를 통해 스크레이퍼를 통과시키거나, 실제 브라우저에서 챌린지를 해결하는 FlareSolverr 인스턴스를 실행하는 것입니다. 두 방법 모두 선택 사항이며, 컨테이너의 환경 변수를 통해 설정합니다.
서버가 해당 사이트에 전혀 접근할 수 없어 발생하는 임포트 실패는 다른 문제입니다. 스크레이퍼를 탓하기 전에 curl -I https://the-site.example/recipe를 사용하여 해당 서버에서 직접 테스트하고 상태 줄을 확인하십시오.
적용 범위
Mealie는 가정에서 처음으로 직접 호스팅하기에 좋은 애플리케이션입니다. 함께 사는 가족들이 별도의 요청 없이도 자연스럽게 사용하기 때문입니다. 이 작업은 Immich로 나만의 사진 라이브러리를 운영하는 것과 성격이 비슷하지만, 훨씬 가볍습니다. 또한 올해 직접 호스팅할 가치가 있는 서비스 목록의 더 넓은 범주에 포함됩니다. 작은 서버 한 대에 두 서비스 모두를 운영할 수 있습니다. Immich만이 유일한 선택지는 아닙니다. 아직 결정하지 못했다면, PhotoPrism과 Immich의 메모리 하한선 및 백업 명령어를 비교해 보는 것이 좋습니다. 디스크 공간을 모두 할당하기 전에 읽어볼 가치가 충분합니다.
FAQ
레시피 URL 가져오기가 실패하는 이유는 무엇입니까?
두 가지 일반적인 원인이 있습니다. 첫째, 해당 페이지가 구조화된 레시피 데이터를 게시하지 않아 스크래퍼가 아무것도 찾지 못하고 재료가 없는 제목만 가져오는 경우입니다. 둘째, 사이트 앞단의 봇 차단 계층이 레시피 대신 챌린지 페이지를 반환하는 경우입니다. 후자의 경우 Mealie를 더 나은 주소 평판을 가진 프록시로 연결하거나, 실제 브라우저에서 챌린지를 해결하는 자체 호스팅 FlareSolverr 인스턴스로 연결할 수 있습니다. 설정을 변경하기 전에 curl -I를 사용하여 서버에서 해당 페이지에 접근할 수 있는지 먼저 확인하십시오.
PostgreSQL이 필요합니까, 아니면 SQLite로 충분합니까?
가정용으로는 SQLite로 충분하며 기본값으로 설정되어 있습니다. 데이터 디렉터리가 네트워크 연결 스토리지(NAS)에 위치할 때는 PostgreSQL로 전환하십시오. 네트워크 파일 시스템에서 SQLite를 사용하면 데이터베이스 잠금 오류가 발생하고 파일이 손상될 수 있습니다. PostgreSQL에서 복원을 수행할 때는 아카이브를 로드하기 전에 모든 데이터를 삭제하므로 데이터베이스 사용자가 슈퍼유저 권한을 가지고 있어야 합니다.
도메인 이름 없이 Mealie를 실행할 수 있습니까?
네, 내부 네트워크에서는 가능합니다. BASE_URL을 http://192.168.1.20:9925과 같이 실제로 입력할 주소로 설정하고 nginx는 건너뛰십시오. 초대 및 비밀번호 재설정 링크는 BASE_URL을 기반으로 생성되므로, 값이 잘못되면 다른 사용자가 열 수 없는 링크가 생성됩니다. 일반 HTTP를 통해 인터넷에 노출하지 마십시오. 로그인 정보가 암호화되지 않은 상태로 전송됩니다.
가족에게 개별 로그인을 제공하려면 어떻게 해야 합니까?
ALLOW_SIGNUP를 "false"으로 설정한 상태에서 관리자 영역을 통해 사용자를 추가하십시오. 그러면 초대 링크가 생성되며 이를 전달하면 됩니다. 주방을 공유하는 모든 구성원을 같은 가구(household)로 묶으면 레시피, 식단 계획, 쇼핑 목록을 공유할 수 있습니다. 한 서버 내에서 가구를 분리하면 각 컬렉션을 별도로 유지할 수 있습니다.
Mealie 실행을 중단하면 레시피는 어떻게 됩니까?
데이터를 추출할 수 있습니다. 관리자 백업 기능을 사용하면 데이터를 JSON 형식으로 저장할 수 있으며, Mealie는 레시피를 일반 마크다운 파일로 내보낼 수도 있습니다. 마크다운 파일은 별도의 소프트웨어 없이도 모든 텍스트 편집기에서 읽을 수 있습니다. 필요하기 전에 미리 내보내기를 한 번 수행하여 파일을 정상적으로 열 수 있는지 확인하십시오.