Created
April 6, 2026 07:20
-
-
Save metasikander/0f2430b11f2da22a00b9c771fb0b9bdd to your computer and use it in GitHub Desktop.
LLM Stack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| services: | |
| litellm: | |
| container_name: "litellm" | |
| environment: | |
| - "PROXY_MASTER_KEY=${LITELLM_PROXY_MASTER_KEY}" | |
| - "LITELLM_SALT_KEY=${LITELLM_SALT_KEY}" | |
| - "LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY}" | |
| - "PROXY_ADMIN_ID=${LITELLM_PROXY_ADMIN_ID}" | |
| - "STORE_MODEL_IN_DB=${LITELLM_STORE_MODEL_IN_DB}" | |
| - "UI_USERNAME=${LITELLM_UI_USERNAME}" | |
| - "UI_PASSWORD=${LITELLM_UI_PASSWORD}" | |
| - "DATABASE_URL=${LITELLM_DATABASE_URL}" | |
| mem_limit: 2g | |
| image: "ghcr.io/berriai/litellm:main-stable" | |
| networks: | |
| - "marvin_default" | |
| - proxy | |
| expose: | |
| - "4000/tcp" | |
| restart: "always" | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.http.routers.litellm.rule=Host(`litellm.public.cc`)" | |
| - "traefik.http.routers.litellm.entrypoints=websecure" | |
| - "traefik.http.routers.litellm.tls.certresolver=cloudflare" | |
| - "traefik.http.services.litellm.loadbalancer.server.port=4000" | |
| - "homepage.group=Tools" | |
| - "homepage.name=LiteLLM" | |
| - "homepage.icon=ollama.png" | |
| - "homepage.href=https://litellm.public.cc/ui" | |
| marvin_cf: | |
| command: | |
| - "tunnel" | |
| - "--no-autoupdate" | |
| - "run" | |
| - "--token" | |
| - "${CF_TUNNEL_TOKEN}" | |
| container_name: "marvin_cf" | |
| image: "cloudflare/cloudflared:latest" | |
| networks: | |
| - "marvin_default" | |
| restart: "always" | |
| marvin_db: | |
| container_name: "marvin_db" | |
| environment: | |
| - "POSTGRES_USER=${POSTGRES_USER}" | |
| - "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}" | |
| - "POSTGRES_DB=${POSTGRES_DB}" | |
| expose: | |
| - "5432/tcp" | |
| image: "postgres:17" | |
| networks: | |
| - "marvin_default" | |
| restart: "always" | |
| volumes: | |
| - "./postgres_data:/var/lib/postgresql/data:z" | |
| marvin_redis: | |
| container_name: "marvin_redis" | |
| image: "redis:latest" | |
| expose: | |
| - "6379/tcp" | |
| networks: | |
| - "marvin_default" | |
| restart: "always" | |
| llama-cpp: | |
| image: ghcr.io/ggml-org/llama.cpp:server-cuda | |
| container_name: llama-cpp | |
| restart: unless-stopped | |
| expose: | |
| - "8080" | |
| volumes: | |
| - "./llama-models:/models:ro" | |
| networks: | |
| - "marvin_default" | |
| deploy: | |
| resources: | |
| reservations: | |
| devices: | |
| - driver: nvidia | |
| count: 1 | |
| capabilities: [gpu] | |
| command: | |
| - "-m" | |
| - "/models/Qwen3.5-9B-Q4_K_M.gguf" | |
| - "--verbose" | |
| - "-ngl" | |
| - "99" | |
| - "-c" | |
| - "131072" | |
| - "-np" | |
| - "1" | |
| - "-fa" | |
| - "on" | |
| - "--cache-type-k" | |
| - "q4_0" | |
| - "--cache-type-v" | |
| - "q4_0" | |
| - "--host" | |
| - "0.0.0.0" | |
| - "--port" | |
| - "8080" | |
| healthcheck: | |
| test: ["CMD", "curl", "-f", "http://localhost:8080/health"] | |
| interval: 30s | |
| timeout: 10s | |
| retries: 10 | |
| start_period: 60s | |
| bitnet: | |
| build: | |
| context: ./bitnet | |
| dockerfile: Dockerfile | |
| image: bitnet-server:latest | |
| container_name: bitnet-server | |
| restart: unless-stopped | |
| cpuset: "0-15" | |
| expose: | |
| - "8080" | |
| networks: | |
| - "marvin_default" | |
| environment: | |
| OMP_PLACES: "cores" | |
| OMP_PROC_BIND: "close" | |
| command: | |
| - "-m" | |
| - "/models/ggml-model-i2_s.gguf" | |
| - "-a" | |
| - "bitnet" | |
| - "--host" | |
| - "0.0.0.0" | |
| - "--port" | |
| - "8080" | |
| - "-t" | |
| - "8" | |
| - "-c" | |
| - "2048" | |
| - "-n" | |
| - "4096" | |
| healthcheck: | |
| test: ["CMD", "curl", "-f", "http://localhost:8080/health"] | |
| interval: 30s | |
| timeout: 10s | |
| retries: 10 | |
| start_period: 60s | |
| open-webui: | |
| container_name: "open-webui" | |
| expose: | |
| - "8080/tcp" | |
| image: "ghcr.io/open-webui/open-webui:main" | |
| mem_limit: 2g | |
| networks: | |
| - "marvin_default" | |
| restart: "always" | |
| volumes: | |
| - "./open-webui:/app/backend/data:z" | |
| hermes-agent: | |
| container_name: "hermes-agent" | |
| build: | |
| context: ./hermes | |
| dockerfile: Dockerfile | |
| entrypoint: ["hermes", "gateway"] | |
| environment: | |
| - "HOME=/home/hermes" | |
| - "API_SERVER_ENABLED=true" | |
| - "API_SERVER_HOST=0.0.0.0" | |
| - "API_SERVER_PORT=8642" | |
| - "API_SERVER_KEY=${HERMES_API_KEY}" | |
| - "OPENAI_API_BASE=http://litellm:4000/v1" | |
| - "OPENAI_API_KEY=${HERMES_OPENAI_API_KEY}" | |
| expose: | |
| - "8642/tcp" | |
| networks: | |
| - "marvin_default" | |
| restart: "always" | |
| volumes: | |
| - "./hermes/config:/home/hermes/.hermes:z" | |
| healthcheck: | |
| test: ["CMD", "curl", "-f", "http://localhost:8642/health"] | |
| interval: 30s | |
| timeout: 10s | |
| retries: 5 | |
| start_period: 30s | |
| hermes-workspace: | |
| container_name: "hermes-workspace" | |
| build: | |
| context: ./hermes-workspace | |
| dockerfile: Dockerfile | |
| depends_on: | |
| hermes-agent: | |
| condition: service_healthy | |
| environment: | |
| - "HERMES_API_URL=http://hermes-agent:8642" | |
| - "HERMES_PASSWORD=${HERMES_PASSWORD}" | |
| - "HERMES_API_KEY=${HERMES_API_KEY}" | |
| expose: | |
| - "3000/tcp" | |
| networks: | |
| - "marvin_default" | |
| - proxy | |
| restart: "always" | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.http.routers.hermes.rule=Host(`hermes.public.cc`)" | |
| - "traefik.http.routers.hermes.entrypoints=websecure" | |
| - "traefik.http.routers.hermes.tls.certresolver=cloudflare" | |
| - "traefik.http.services.hermes.loadbalancer.server.port=3000" | |
| - "homepage.group=Tools" | |
| - "homepage.name=Hermes Workspace" | |
| - "homepage.icon=hermes.png" | |
| - "homepage.href=https://hermes.public.cc" | |
| searxng: | |
| container_name: "searxng" | |
| image: "docker.io/searxng/searxng:latest" | |
| restart: "always" | |
| networks: | |
| - "marvin_default" | |
| - "proxy" | |
| expose: | |
| - "8080/tcp" | |
| volumes: | |
| - "./searxng:/etc/searxng:rw" | |
| environment: | |
| - "SEARXNG_BASE_URL=https://searxng.public.cc/" | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.http.routers.searxng.rule=Host(`searxng.public.cc`)" | |
| - "traefik.http.routers.searxng.entrypoints=websecure" | |
| - "traefik.http.routers.searxng.tls.certresolver=cloudflare" | |
| - "traefik.http.services.searxng.loadbalancer.server.port=8080" | |
| - "homepage.group=Tools" | |
| - "homepage.name=SearXNG" | |
| - "homepage.icon=searxng.png" | |
| - "homepage.href=https://searxng.public.cc" | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "1m" | |
| max-file: "1" | |
| networks: | |
| marvin_default: | |
| name: "marvin_default" | |
| proxy: | |
| external: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM node:22-alpine | |
| WORKDIR /app | |
| # Install dependencies | |
| RUN apk add --no-cache curl git | |
| # Cache-busting layer — forces re-clone when main branch updates | |
| ADD https://github.com/outsourc-e/hermes-workspace/commits/main.atom /tmp/latest_commit | |
| # Clone the repository | |
| RUN git clone --depth 1 https://github.com/outsourc-e/hermes-workspace.git . | |
| # Install pnpm and dependencies | |
| RUN npm install -g pnpm && pnpm install --no-frozen-lockfile | |
| # Build the app | |
| ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 | |
| RUN pnpm build | |
| # Runtime environment | |
| ENV NODE_ENV=production | |
| EXPOSE 3000 | |
| CMD ["node", "server-entry.js"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM ubuntu:latest | |
| # Install updates and dependencies | |
| RUN apt-get update && \ | |
| apt-get upgrade -y && \ | |
| apt-get install -y \ | |
| curl \ | |
| ca-certificates \ | |
| git \ | |
| sudo \ | |
| npm \ | |
| && apt-get clean \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Create a non-root user (handle pre-existing GID/UID gracefully) | |
| ARG USERNAME=hermes | |
| ARG USER_UID=1000 | |
| ARG USER_GID=1000 | |
| RUN if getent group ${USER_GID} > /dev/null 2>&1; then \ | |
| existing_group=$(getent group ${USER_GID} | cut -d: -f1); \ | |
| groupmod -n ${USERNAME} "$existing_group"; \ | |
| else \ | |
| groupadd --gid ${USER_GID} ${USERNAME}; \ | |
| fi && \ | |
| if getent passwd ${USER_UID} > /dev/null 2>&1; then \ | |
| existing_user=$(getent passwd ${USER_UID} | cut -d: -f1); \ | |
| usermod -l ${USERNAME} -d /home/${USERNAME} -m "$existing_user"; \ | |
| else \ | |
| useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME}; \ | |
| fi && \ | |
| echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers | |
| # Switch to non-root user | |
| USER ${USERNAME} | |
| WORKDIR /home/${USERNAME} | |
| # Set environment variables | |
| ENV HOME=/home/${USERNAME} | |
| ENV PATH="/home/${USERNAME}/.local/bin:/home/${USERNAME}/.cargo/bin:${PATH}" | |
| # ── Step 1: Install uv ── | |
| RUN curl -LsSf https://astral.sh/uv/install.sh | sh | |
| # ── Step 2: Check for repo changes ── | |
| ADD https://github.com/NousResearch/hermes-agent/commits/main.atom /tmp/latest_commit | |
| # ── Step 3: Clone the repository and init submodules ── | |
| RUN git clone --depth 1 https://github.com/NousResearch/hermes-agent.git | |
| WORKDIR /home/${USERNAME}/hermes-agent | |
| RUN git submodule update --init --recursive --depth 1 | |
| # ── Step 4: Create venv with Python 3.11 ── | |
| RUN uv venv venv --python 3.11 | |
| # ── Step 5: Install Python dependencies ── | |
| ENV VIRTUAL_ENV=/home/${USERNAME}/hermes-agent/venv | |
| RUN uv pip install -e ".[all]" | |
| # ── Step 6: Install submodule packages (optional) ── | |
| RUN uv pip install -e "./tinker-atropos" | |
| # ── Step 7: Install Node.js dependencies (browser tools & WhatsApp) ── | |
| RUN npm install | |
| # ── Step 8: Create configuration directory structure ── | |
| RUN mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills,pairing,hooks,image_cache,audio_cache,whatsapp/session} && \ | |
| cp cli-config.yaml.example ~/.hermes/config.yaml && \ | |
| touch ~/.hermes/.env | |
| # ── Step 9: Symlink hermes to PATH ── | |
| RUN mkdir -p ~/.local/bin && \ | |
| ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes | |
| # Expose default port | |
| EXPOSE 8080 | |
| # Persistent volumes for config and data | |
| VOLUME ["/home/hermes/.hermes"] | |
| # Default entrypoint | |
| ENTRYPOINT ["hermes-agent"] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment