Self-hosting AnythingLLM is one docker run away, and then a redeploy quietly eats every workspace you made. That is not a bug in AnythingLLM — it is three settings the image expects you to get right, and none of them are the ones you would guess.
I maintain a one-click Railway template for it, so I have hit all three. Here they are for anyone running it on Docker, Compose, Fly, Render, Coolify or anything else.
1. STORAGE_DIR, and a volume actually mounted there
AnythingLLM keeps everything you care about on disk: uploaded documents, the parsed vector cache, the LanceDB vector store, workspace and user records in SQLite. All of it lives under STORAGE_DIR.
docker run -d \
-e STORAGE_DIR=/store \
-v anythingllm:/store \
-p 3001:3001 \
mintplexlabs/anythingllm
Skip the volume and the container still works perfectly — until the first redeploy, when your workspaces, embeddings and users are gone. It reads like data loss and it is really an unmounted path. This is the failure that costs people a weekend of re-embedding, because the app gives no warning: an empty STORAGE_DIR is a valid first run.
2. The process needs to own that directory
The image writes to STORAGE_DIR as it boots — SQLite migrations, LanceDB's on-disk tables, the documents cache. A fresh volume mounted into a container running as a non-root user is owned by root, and AnythingLLM comes up and then fails on write with permissions errors that mention SQLite rather than the volume.
On Docker, either run as root or chown the mount to the app's UID before the first boot. On Railway, that is what RAILWAY_RUN_UID=0 does — the platform runs containers as a non-root user by default, so this is the one variable that has nothing to do with AnythingLLM and everything to do with where you are running it. Whatever the platform, the check is the same: can the process that boots the app write to the volume?
3. The image does not read an injected $PORT
AnythingLLM listens on 3001 and stays there. Platforms that hand you a port in $PORT and expect the app to follow it (Railway, Heroku, Cloud Run) will route to the wrong place and you get a 502 at the edge while the container logs look completely healthy.
The fix is to pin it — set PORT=3001 so the platform's proxy targets the port the app is actually on, rather than hoping the app moves. And point the healthcheck at /; there is no dedicated health endpoint, and using a deeper route means your probe carries a session it does not have.
Sizing, honestly
The default embedder runs locally, so the container is doing model work on CPU on top of Node. It is comfortable around 2GB of RAM, and the vector store grows with what you upload rather than with how many people use it. If you point it at a hosted embedder instead (OpenAI, Cohere, or your own Ollama), the memory picture gets a lot flatter and the disk still grows.
The one-click version
If you would rather not wire those three things yourself: I maintain a Railway template that ships them already set — volume at /store, STORAGE_DIR pointing at it, RAILWAY_RUN_UID=0, PORT=3001, healthcheck on /. Full disclosure: I get a template kickback if you deploy through this link.
👉 Deploy AnythingLLM on Railway
It is the same open image either way — mintplexlabs/anythingllm, Mintplex-Labs/anything-llm — so nothing above is locked to one platform. If you run it somewhere else, the three settings are still the three settings.
If you have hit a fourth one, I would like to hear it — I would rather fix the template than have people copy a gap in it.
Top comments (0)