Mealie for VPS: Run Your Own Recipe Manager
Run Mealie for your VPS with Docker Compose. Paste recipe links to get clean steps, then manage meal plans and shopping lists with nginx, TLS, and backups.
Wetin self-hosted recipe manager dey do
Self-hosted recipe manager dey keep your recipes for database on server wey you own, and Mealie na the one wey most households dey choose. You paste the address of recipe page, and Mealie go read the ingredients, the steps, the yield, and the cook time from am, then leave the story and advertising behind. Na the food go enter your collection.
The rest of the app small. You get weekly meal plan wey you fit drag recipes enter, plus shopping list wey dey build from that plan. Each person wey dey cook get im own login. Everything dey run inside one container, and e dey idle between requests, so modest VPS fit carry am without stress.
This guide dey use Docker Compose. If the words services: and volumes: new to you, read how Docker Compose files dey put together first, because everything wey follow na one compose file and four commands.
Mealie Docker Compose wey Install
Mealie dey publish im images go GitHub container registry. As of October 2026, the current stable tag na v3.28.0. Pin one version instead of using latest: with latest, one docker compose pull for another day fit move you across database migration wey you never ready for.
sudo mkdir -p /srv/mealie
cd /srv/mealie
sudo nano docker-compose.ymlservices:
mealie:
image: ghcr.io/mealie-recipes/mealie:v3.28.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:Two lines deserve make you check dem before you start am.
The port dey written as 127.0.0.1:9925:9000, no be 9925:9000. The container dey listen for 9000 inside, while the host dey map 9925 to am. If you bind that map to loopback address, nginx fit reach Mealie but internet no fit. Docker dey write im own rules inside packet filter, so plain 9925:9000 fit still reach from outside even when your firewall talk say the port dey closed. You need understand this surprise once: see why published Docker ports dey ignore ufw.
BASE_URL must be the exact public address wey you go use, with the scheme and without trailing slash. Mealie dey use am build password reset links and invitation links. Set am to http://localhost:9925, and the invitation wey you send your partner go contain link wey only work for the server itself.
Start am and monitor the first boot.
sudo docker compose up -d
sudo docker compose logs -f mealieThe first start go create the SQLite database and run the migrations, and this go take few seconds. When the log settle and stop printing migration lines, check the app locally.
curl -I http://127.0.0.1:9925A 200 OK mean say the app dey up. Connection refused mean say the container no dey run: run sudo docker compose ps and read the exit code. If container stop with code 137, e mean say e exceed the 1000M memory limit and dem kill am. This fit happen for the smallest plans.
First login, and how to turn off open signup
The default account na changeme@example.com with password MyPassword. Log in with am, then change both immediately, because dem dey printed for the documentation and every scanner know dem.
ALLOW_SIGNUP: "false" for the compose file na deliberate. If signup dey open, anybody wey find the address fit create account for your recipe box. If you close am, you go add people from the admin area. This one go generate invitation link wey you go send to dem yourself. The link dey built from BASE_URL, na why the value important. If you later run several apps for the same server and want use one password for all of dem, Mealie fit hand over login to external identity provider like self-hosted Authentik instance.
Mealie dey put users inside household, and households dey inside group. Everybody for one household dey share meal plan and shopping list, na wetin family need. Recipe collection wider pass that: households dey share their recipes, tags, and categories with every other household for the same group. So, separate households for one server get separate plans and lists, but dem still cook from the same recipes. This one still fit flatshare where nobody agree about anchovies. If people need fully separate collections, give dem separate groups, because nothing dey shared between groups.
Importer wey be the main reason to run this
Open recipe collection, choose make recipe from URL, then paste link. Mealie go fetch the page and search for structured recipe data, wey be machine-readable block wey most recipe websites dey add for search engines. If that block dey there, the import clean and e happen immediately.
If you configure AI provider, you fit also import from image or plain text wey you paste, including photograph of cookbook page. Image import need provider wey fit read images. Without provider, you no fit use these import options. Dem dey pass through slower process and need checking afterwards, because handwritten fraction fit easy misread.
Bulk imports dey run from the same screen: paste list of addresses, one for each line, and Mealie go process dem for background. Collection of two hundred bookmarks fit move across for one sitting.
Meal plan and shopping list
The meal planner na calendar. Drag recipe put am for one day and e don plan. Shopping list go collect ingredients from all the recipes wey you plan, put dem for one list, and combine duplicate items. So, if two recipes need onions, e go show one line instead of two.
The list na live page wey dey your phone as you dey shop. Because na your own server dey hold am, everybody for the house go see the same list at the same time. If one person tick milk, e go disappear from the other person's screen too.
Put nginx and TLS for front
Mealie dey speak plain HTTP and e no get certificate handling by itself. Make nginx for front handle transport layer security (TLS). First point DNS A record go your server, because certificate step go verify that name.
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 printing syntax is ok and test is successful na the gate. Only reload after e pass, because reloading broken config go leave the old one running and hide the mistake until the next restart.
client_max_body_size 64M dey there because nginx default na 1 MB. Uploading recipe photo or restoring backup through browser fit send body wey pass that size. Without that line, nginx go return 413 Request Entity Too Large, no be Mealie, so application log no go show anything.
Then issue the certificate. That step and the renewal timer dey covered for issuing a Let's Encrypt certificate for nginx with certbot.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d recipes.example.comCertbot go rewrite the server block to listen on 443 and add redirect from port 80. Load the site over https:// and confirm say browser accept the certificate. If Mealie load but its own links send you go http://, then BASE_URL still dey say http and you need correct am. After that, run sudo docker compose up -d to recreate the container with the new value.
Serving Mealie under subpath like example.com/recipes no work, because frontend no fit serve from subpath. Use subdomain.
Backups, and wetin restore really dey do
Everything wey Mealie own dey inside /app/data/ for inside the container, wey be the mealie-data volume. Copy that volume and you don copy the recipes, images, and database together. Mealie documentation call stopping the container and copying this folder the best way to back up SQLite install.
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 mealieThe volume name get project name as prefix, and project name na the directory wey hold compose file. From /srv/mealie, the volume na mealie_mealie-data, na why the first command be docker volume ls: use the name wey e print, no be the name for this guide. Stopping the container first matter, because SQLite dey often write at that time, and live copy fit restore as unreadable data.
Mealie still get im own backup page for admin area, wey dey write zip archive wey hold the database as database.json. No depend on am to carry you across version change: na the same Mealie version wey make the archive suppose restore am, and restore with different version go stop with error. To move between servers, run the same pinned version for the new server, then copy the volume go there or restore the archive there. Restoring one na destructive action by design: e go delete the current database before e load the archive, and you no fit undo am. E go log you out when e finish.
Neither copy be backup while e still dey for the same server. Push the archive go another place on schedule, na wetin encrypted off-server backups with restic dey do.
Update Mealie
cd /srv/mealie
sudo nano docker-compose.yml
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs -f mealieRaise the pinned version for the file, then pull and recreate am. Migrations go run when the new image start for the first time. Make copy of the volume before major version change, because if migration fail halfway, e go leave database wey the previous image no go fit open again. Read the release notes for all the versions between your current version and the new one.
When importer dey fail
Some sites no dey publish structured recipe data at all, so Mealie go import title with ingredient list wey empty. You no fit configure this problem comot. Paste recipe text manually instead.
Other failures dey come from bot protection wey dey in front of recipe site. E dey answer Mealie with challenge page instead of the recipe. Mealie don already dey act like browser and dey rotate user agent to reduce this problem. If site still refuse, documented options na to send scraper through proxy wey get better address reputation, or run FlareSolverr instance wey go solve challenge inside real browser. Both options na optional, and you go set both through environment variables for the container.
If import fail because your server no fit reach the site at all, na different problem be that. Test am from the server with curl -I https://the-site.example/recipe and read status line before you blame scraper.
Where e fit
Mealie na good first self-hosted application for house, because people wey dey live with you go use am without you asking dem. E get the same kind work as running your own photo library with Immich, though e lighter well well, and e dey among the wider list of things wey worth self-hosting this year. One small server fit hold both. Immich no be the only option for that second work, and if you never decide, the memory floors and backup commands of PhotoPrism and Immich get enough difference say e worth reading before you give out the remaining disk space. If the household dey keep plans and notes together with dinner recipes, a self-hosted AFFiNE workspace na another compose-file exercise, but e need four containers and much more memory than Mealie, so first check wetin remain for the box. And if the household wan track training together with dinner, openGym keeps the workouts and e need the same pinned tag, compose file and certificate wey you just work through. The evening after dinner get its own version of this, because Halcyon rebuilds an existing Jellyfin library as a 1990s rental shop wey you fit walk around, and na another small container in front of something wey you already run, instead of another database wey you need back up. No be everything for this family be household app, and if the same habit ever need carry something for work, a self-hosted Chatwoot support desk na the same pinned tag and certificate again, with Postgres, Redis and working outbound mail behind am. That one na much heavier tenant than recipe box, so e deserve im own server.
FAQ
Import recipe URL no work. Why?
Two common causes dey. Either the page no publish structured recipe data, so scraper no find anything and you get title without ingredients, or bot-protection layer for front of the site return challenge page instead of the recipe. For the second case, you fit point Mealie to proxy wey get better address reputation, or to self-hosted FlareSolverr instance wey dey solve the challenge inside real browser. Confirm say your server fit reach the page at all with curl -I before you change anything.
I need PostgreSQL, or SQLite enough?
SQLite enough for household, and na the default. Move to PostgreSQL when data directory dey for network attached storage, because SQLite over network filesystem dey produce locked-database errors and fit corrupt the file. Restores under PostgreSQL need database user to be superuser, because restore go delete everything before e load the archive.
I fit run Mealie without domain name?
Yes, for your own network. Set BASE_URL to the address wey you go actually type, like http://192.168.1.20:9925, and skip nginx. Invitation and password reset links dey build from BASE_URL, so wrong value go produce links wey nobody else fit open. No expose am to internet over plain HTTP, because login go then dey sent in the clear.
How I go give my family their own logins?
Leave ALLOW_SIGNUP set to "false" and add people from admin area. This one go produce invitation link wey you send them. Put everybody wey dey share kitchen for the same household so dem fit share meal plan and shopping list. Recipes dey shared with every household for the group either way, so people wey need separate collections need separate groups.
Wetin go happen to my recipes if I stop running Mealie?
Dem go come out. Admin backup dey write your data as JSON, and Mealie fit also export recipes as plain markdown files. The files go remain readable for any text editor without any software at all. Take one export before you need am, and check say you fit open am.