freellmapi Docker & Self-Hosted Deployment

GitHub

OpenAI-compatible proxy that stacks the free tiers of 28 LLM providers (~4B tokens/month) behind one /v1 endpoint โ€” plus any custom OpenAI-compatible endpoint.

17,564 stars TypeScript TypeScript 1 file ยท ~7,989 tokens
RAW Compose
1. Pull Image (GHCR) docker pull ghcr.io/tashfeenahmed/freellmapi
2. Docker Run (1-Click Launch) docker run -d --name freellmapi -p 3001:3001 --restart always ghcr.io/tashfeenahmed/freellmapi:latest
3. Podman Run podman run -d --name freellmapi -p 3001:3001 --restart always ghcr.io/tashfeenahmed/freellmapi:latest
4. Skopeo Inspect (Zero-Pull Manifest) skopeo inspect docker://ghcr.io/tashfeenahmed/freellmapi:latest
5. ORAS Pull (OCI Artifact) oras pull ghcr.io/tashfeenahmed/freellmapi:latest
6. Compose Deployment docker compose up -d
docker-compose.yml
Download .yml
services:
  freellmapi:
    image: ghcr.io/tashfeenahmed/freellmapi:latest
    build:
      context: .
      dockerfile: Dockerfile
    env_file:
      - .env
    environment:
      NODE_ENV: production
      PORT: 3001
      # Settings โ†’ Check for updates โ†’ "Update now". Only active when the
      # `autoupdate` profile below runs and WATCHTOWER_TOKEN is set in .env.
      FREELLMAPI_SELF_UPDATE_URL: http://watchtower:8080
      FREELLMAPI_SELF_UPDATE_TOKEN: ${WATCHTOWER_TOKEN:-}
    labels:
      # The updater below only ever touches containers carrying BOTH labels.
      # The scope keeps it off containers other stacks have opted into their
      # own Watchtower, and keeps theirs off this one.
      com.centurylinklabs.watchtower.enable: "true"
      com.centurylinklabs.watchtower.scope: freellmapi
    # Bound to localhost by default โ€” FreeLLMAPI is single-user and must not be
    # exposed to the internet. Set HOST_BIND=0.0.0.0 to reach it from your LAN.
    ports:
      - "${HOST_BIND:-127.0.0.1}:${PORT:-3001}:3001"
    volumes:
      - freellmapi-data:/app/server/data
    # Makes `host.docker.internal` resolve to the host from inside the
    # container. Docker Desktop provides it already; plain Docker on Linux does
    # not, and without it the documented way to reach a proxy running on the
    # host (PROXY_URL=http://host.docker.internal:7890) fails to resolve.
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: unless-stopped
    healthcheck:
      test:
        [
          "CMD",
          "node",
          "-e",
          "fetch('http://127.0.0.1:3001/api/ping').then((res) => { if (!res.ok) process.exit(1); }).catch(() => process.exit(1));"
        ]
      interval: 30s
      timeout: 5s
      start_period: 15s
      retries: 3

  # Opt-in self-update: `docker compose --profile autoupdate up -d` with
  # WATCHTOWER_TOKEN set in .env. Watchtower (the maintained nicholas-fedor
  # fork) runs with only its token-protected update endpoint, which the
  # dashboard calls when you click "Update now"; it pulls the newer image and
  # recreates the freellmapi container with the same volume and settings.
  # Nothing happens on a schedule, no port is published, and no other
  # container is touched. It needs the Docker socket, which is root-equivalent
  # on the host โ€” leave the profile off if that is not acceptable.
  watchtower:
    image: nickfedor/watchtower:1.22.3
    profiles: ["autoupdate"]
    environment:
      WATCHTOWER_HTTP_API_ENDPOINTS: update
      WATCHTOWER_HTTP_API_TOKEN: ${WATCHTOWER_TOKEN:-}
      WATCHTOWER_LABEL_ENABLE: "true"
      WATCHTOWER_SCOPE: freellmapi
      WATCHTOWER_CLEANUP: "true"
    labels:
      com.centurylinklabs.watchtower.scope: freellmapi
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped

volumes:
  freellmapi-data: