How to Self-Host FreeLLMAPI Using Docker and Docker Compose
FreeLLMAPI ships as a multi-stage Docker image that compiles the full TypeScript workspace and executes the compiled runtime as a non-root node user, enabling secure, one-command deployment via Docker Compose.
This guide covers the production deployment of FreeLLMAPI from the tashfeenahmed/freellmapi repository. The containerized architecture isolates the Node.js 20 runtime, persists SQLite data through named volumes, and exposes a health-checked API gateway on port 3001.
Multi-Stage Dockerfile Architecture
The Dockerfile at the repository root implements a three-stage build pipeline to minimize image size and attack surface.
Stage 1: Dependency Installation (deps)
The deps stage starts from node:20-bookworm-slim and installs build-time system packages required for native module compilation.
# Installs Python, make, g++, then runs npm ci
This stage fetches all workspace dependencies using npm ci, ensuring reproducible builds by respecting the lockfile.
Stage 2: Compilation (build)
The build stage copies the full source tree and executes npm run build to transpile TypeScript into JavaScript. After compilation, it runs npm prune --omit=dev to strip development dependencies, reducing the final image footprint.
Stage 3: Production Runtime (runtime)
The final runtime stage creates a hardened execution environment:
- Starts from a clean
node:20-bookworm-slimimage - Copies only production
node_modulesand compiled assets (server/dist,client/dist) - Creates a writable
/app/server/datadirectory owned by the non-rootnodeuser - Sets environment variables:
NODE_ENV=production,PORT=3001,FREELLMAPI_INSTALL_METHOD=docker - Defines a health-check that curls
/api/pingevery 30 seconds - Executes
docker-entrypoint.shto fix volume permissions before dropping privileges and runningnode server/dist/index.js
Docker Compose Configuration
The docker-compose.yml orchestrates the container with persistent storage and network isolation.
Volume Persistence
The compose file declares a named volume freellmapi-data mounted at /app/server/data. This preserves the SQLite database, encryption key files, and server logs across container restarts and image updates.
Network Binding and Security
By default, the service binds to 127.0.0.1:3001, restricting access to the local machine. To expose FreeLLMAPI across your LAN, modify the HOST_BIND environment variable to 0.0.0.0 in your .env file.
The configuration also injects host.docker.internal as an extra host, enabling outbound proxy URLs that resolve to the host machine when running on Linux Docker.
Step-by-Step Deployment
Deploy a production instance in five commands:
- Clone the repository
git clone https://github.com/tashfeenahmed/freellmapi.git
cd freellmapi
- Configure environment variables
cp .env.example .env
Edit .env and generate a secure 64-character hexadecimal encryption key:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Set this as ENCRYPTION_KEY in your .env file. Configure optional settings like PROXY_URL or HOST_BIND as needed.
- Build and start the container
docker compose up -d
This command builds the multi-stage image if necessary, creates the persistent volume, and starts the health-checked container in detached mode.
- Verify deployment health
docker compose ps
curl http://127.0.0.1:3001/api/ping
A successful deployment returns {"ok": true} from the ping endpoint and shows a "healthy" status in the container list.
- Access the dashboard
Navigate to http://127.0.0.1:3001 in your browser. If you changed HOST_BIND to 0.0.0.0, replace 127.0.0.1 with your host machine's IP address.
Data Persistence and Backup
All application state resides in /app/server/data inside the container. The docker-compose.yml maps this to the named volume freellmapi-data. To inspect or back up the SQLite database:
# Create a backup
docker cp freellmapi:/app/server/data ./freellmapi-backup
The docker-entrypoint.sh script ensures the node user owns this directory on startup, preventing permission errors when the container writes logs or database files.
Summary
- FreeLLMAPI uses a hardened, multi-stage Dockerfile with separate
deps,build, andruntimephases to minimize the production attack surface. - The container runs as the non-root
nodeuser and exposes port 3001 with a built-in health check on/api/ping. - Docker Compose manages a persistent named volume (
freellmapi-data) that stores the SQLite database and logs at/app/server/data. - Default configuration binds to localhost (
127.0.0.1:3001) for single-user security; changeHOST_BINDto0.0.0.0for LAN access. - Deployment requires cloning the repository, configuring a 64-character
ENCRYPTION_KEYin.env, and runningdocker compose up -d.
Frequently Asked Questions
How do I generate the required ENCRYPTION_KEY?
Run the Node.js one-liner provided in the setup instructions: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". This outputs a 64-character hexadecimal string that you must paste into your .env file as the ENCRYPTION_KEY variable. The key encrypts sensitive data in the SQLite database and must be backed up; losing it renders stored credentials unrecoverable.
Where is the SQLite database stored inside the container?
The database resides in /app/server/data within the container filesystem. The docker-compose.yml mounts the named volume freellmapi-data to this path, ensuring data survives container restarts and image updates. You can access this directory using docker exec -it freellmapi /bin/sh or by copying files out with docker cp.
How do I expose FreeLLMAPI to other devices on my network?
Edit the .env file and set HOST_BIND=0.0.0.0, then restart the container with docker compose restart. By default, Docker Compose binds to 127.0.0.1:3001, which restricts access to the host machine. Changing this binds the port on all network interfaces, allowing other LAN devices to reach the API at http://<host-ip>:3001.
How do I upgrade FreeLLMAPI to a new version?
Pull the latest code with git pull origin main, then rebuild and restart: docker compose up -d --build. The named volume preserves your database and configuration, while the multi-stage build process compiles the updated TypeScript source. Verify the upgrade with docker compose ps and check the health status returns "healthy".
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →