____ _ _ _____ _
/ ___| | __ _ ___(_) ___ _ __| ___| | _____ __
| | _| |/ _` |/ __| |/ _ \ '__| |_ | |/ _ \ \ /\ / /
| |_| | | (_| | (__| | __/ | | _| | | (_) \ V V /
\____|_|\__,_|\___|_|\___|_| |_| |_|\___/ \_/\_/
A lightweight toolkit for managing local AI inference servers (llama.cpp / stable-diffusion.cpp / audio.cpp) on Linux, with hot-swap model presets, embedding server support, and automatic container orchestration. Includes Web UI, TUI and pi.dev container for software development.
GlacierFlow runs llama.cpp inside Docker and lets you switch between model presets on the fly — no manual container restarts needed. A background daemon watches your config and applies changes automatically.
Current features:
- llama.cpp / stable-diffusion.cpp / audio.cpp inference server managed via Docker Compose
- Hot-swap between inference presets (YAML configs)
- Automatic container restarts when preset changes
- Preset loading time tracking & status monitoring
- Example configs for various GPU setups
- Docker-based code generation assistant (pi.dev)
- Hermes AI agent container with web dashboard and kanban task support
- Multi-model benchmarking suite
- GPU layers autotune — binary search for optimal VRAM offload with stability testing and quick benchmark
- MTP preset grid generator — sweep
--spec-draft-n-max/--spec-draft-p-minto generate a 9×9 grid of inference presets - Web-based UI for preset management and status monitoring
- Embedding server (llama.cpp) for vector embeddings, toggleable via override config
- FIFO proxy for serializing chat requests (prevents stream cut-offs during model hot-swap)
- Automatic LLM ↔ Stable Diffusion mode switching in the FIFO proxy (chat and image generation through one endpoint, e.g. Cherry Studio)
- Caddy reverse proxy with SSL/TLS termination and basic auth for Web UI, pi.dev TTYD, Hermes TTYD and VS Code
- User/group ID customization via
GF_USER_ID/GF_USER_GROUP_IDenvironment variables (affects all containers)
- Linux (tested on Arch/Manjaro)
- Docker & Docker Compose
- GPU drivers (Vulkan for AMD/NVIDIA)
- Bash, curl, git, grep, awk, md5sum, openssl, unzip, and other coreutils
git clone https://github.com/howanski/GlacierFlow.git
cd GlacierFlowPick an example preset and copy it to your local presets directory:
cp data/shared/inference_presets/examples/vulkan_8gb_gemma4_e4b_q4.yml \
data/shared/inference_presets/local/By default a self-signed certificate is auto-generated on first run. To use your own certificates, place cert.pem and key.pem in data/ssl_certs/ before starting the daemon.
# Generate self-signed cert manually (daemon does this automatically if files are missing)
openssl req -x509 -newkey rsa:4096 -keyout data/ssl_certs/key.pem -out data/ssl_certs/cert.pem -days 3650 -nodesCreate a .env file with your paths:
GF_AUTO_MERGE_PRESETS= # optional: merge preset files on daemon start, e.g. "A+B=C;D+E=F" (see "Auto-merging presets")
GF_HERMES_AUTOSTART=0 # set to 1 to auto-start Hermes container with the daemon
GF_HERMES_TTYD_PORT=7682
GF_HERMES_WEB_PORT=7683
GF_HERMES_WORKDIR=/path/to/hermes/workdir
GF_USER_GROUP_ID=1000 # group ID for all containers (defaults to 1000)
GF_USER_ID=1000 # user ID for all containers (defaults to 1000)
GF_AUDIO_CPP_ENABLE=0 # set to 1 to auto-clone/build audio.cpp binaries on daemon start (CPU-heavy)
GF_DEFAULT_MODEL_LLM= # preset name for chat mode, loaded by the daemon on start (see "Automatic Mode Switching")
GF_DEFAULT_MODEL_SD= # preset name for image generation mode (see "Automatic Mode Switching")
GF_STABLE_DIFFUSION_ENABLE=0 # set to 1 to auto-download/update stable-diffusion.cpp binaries on daemon start
GF_VSCODE_AUTOSTART=0 # set to 1 to auto-start VS Code container with the daemon
GF_VSCODE_WEB_PORT=7684
GF_HTTP_PASSWORD=glacierflow
GF_HTTP_USER=glacierflow
GF_LLAMA_MEMORY_LIMIT=50g
GF_LLAMA_SERVER_VERSION=server-vulkan
GF_LOG_STATS=0 # set to 1 to enable performance stats logging
GF_MODELS_DIRECTORY=/path/to/your/models
GF_PI_DEV_TTYD_PORT=7681
GF_PI_DEV_WORKDIR=/path/to/coder/workdir
GF_PROXY_AUTO_MODE=0 # set to 1 to enable automatic LLM ↔ SD mode switching in the FIFO proxy
GF_PROXY_DUMP_REQUESTS=0 # set to 1 to dump request/response pairs to JSON files
GF_WEB_UI_PORT=8090Run the background service to spin up the inference server:
cd scripts
./glacierflow_host_daemonPersonally I've just added it to my crontab @reboot via thin wrapper in case it blows up (and fancy blocker when I launch Steam)
For a one-shot check (no daemon loop):
cd scripts
./glacierflow_host_daemon --onceList available presets:
cd scripts
./glacierflow_inference_select_presetLoad a preset by name or hash:
cd scripts
./glacierflow_inference_select_preset vulkan_8gb_gemma4_e4b_q4
./glacierflow_inference_select_preset <md5hash>GlacierFlow includes a web-based management interface for monitoring and controlling the inference server and presets.
Make sure GF_WEB_UI_PORT is set in your .env file:
GF_WEB_UI_PORT=8090Start the daemon first:
cd scripts
./glacierflow_host_daemonOpen https://localhost:8090 in your browser. The UI provides:
- Preset management — list, select, and track active presets
- Status monitoring — (almost) real-time server status (RUNNING, LOADING, STOPPED, etc.)
- Lock/unlock — to force keep inference server off when in locked state
- Stats Viewer — Chart.js-based visualization of inference performance metrics (fill TPS, gen TPS, draft acceptance rate)
- pi.dev httpd — direct link to the TTYD web terminal for the code generation assistant
- hermes httpd — direct link to the TTYD web terminal for the Hermes agent
- hermes Web UI — direct link to the Hermes dashboard
- VS Code — direct link to the code-server instance
The web UI communicates directly with the shared data directory, so it stays in sync with the daemon and CLI tools.
Preset files are Docker Compose YAML snippets that override the base compose.yml. They define:
- command:inference server arguments (model path, context size, GPU layers, diffusion models, etc.)
- devices: GPU device passthrough (e.g.,
/dev/drifor AMD) - volumes: Model directory mount paths
Preset names are derived from their filename (.yml extension stripped). You can reference them by full name or by MD5 hash.
Example presets live in inference_presets/examples/ (tracked). Your custom presets go in inference_presets/local/ (gitignored).
scripts/gf_auto_merge_presets merges preset files by plain concatenation (no parsing, no dedup) inside data/shared/inference_presets/local/. It is driven entirely by GF_AUTO_MERGE_PRESETS in your .env:
GF_AUTO_MERGE_PRESETS="A.ini+B.ini=C.ini;D.ini+E.ini=F.ini"- Sequences are separated by
;and executed strictly in order, so an earlier sequence's output can feed a later one (e.g.A.ini+B.ini+C.ini=E.ini;D.ini+E.ini=F.ini). - Within a sequence, inputs are separated by
+and concatenated in the given order into the output file named after=. - Unset or empty → the script does nothing and exits 0.
The daemon runs it automatically on start (once, before the services come up) when the variable is set. You can also run it manually at any time:
cd scripts
./gf_auto_merge_presetsOn any error (malformed sequence, missing input, output colliding with an input) the script aborts with a non-zero exit code; the daemon keeps running, so fix the .env and re-run.
GlacierFlow includes a benchmark runner that measures throughput (tokens/sec) across multiple models and problem types:
# Show results table (presets × benchmarks)
cd scripts
./glacierflow_benchmark
# Run benchmarks for the currently loaded model (default 3 passes per benchmark)
cd scripts
./glacierflow_benchmark run
# Run benchmarks with a custom number of passes per benchmark
cd scripts
./glacierflow_benchmark run 5The runner:
- Executes each benchmark N times and reports the average TPS (default 3; set via the
passesargument or the interactive prompt) - Skips benchmarks already completed for a given model hash
- Stores results in
data/shared/benchmarks/<benchmark_name>.txt(one line per model hash) - Requires the inference server to be running (
RUNNINGstatus)
| File | Type | Description |
|---|---|---|
c1.json |
SQL | Generate a MySQL query from a schema |
c2.json |
Bash | Write a file-sorting script |
c3.json |
PHP | Optimize a user-processing loop |
c4.json |
Python | Add token-bucket rate limiting to an API gateway |
c5.json |
TypeScript | Refactor a checkout service into a typed module |
c6.json |
Go | Fix a data race in a shared counter |
c7.json |
HTML,CSS,Javascript | Create chart based on pasted yml data |
gf_benchy is a generic, prompt-agnostic benchmark that measures raw prompt-processing (PP) and token-generation (TG) throughput at various KV-cache depths. It runs llama-benchy (via uvx) against the currently loaded model, filling the context to a chosen depth (2k / 32k / 132k) and reporting mean TPS. Results are cached per model hash and depth, so presets can be benchmarked one by one without re-running completed ones.
cd scripts
./gf_benchyThe interactive menu lets you run a single depth, run across all presets, list results, or switch presets.
Note: Because the test texts are generic prose (not real coding tasks),
gf_benchyis good for pinpointing the best general model configuration, but it is not a reliable way to tune MTP (multi-token prediction) settings. For MTP tuning, preferglacierflow_benchmark, which uses real-life coding tasks.
GlacierFlow includes a GPU layers autotune script that finds the optimal number of layers to offload to the GPU. It uses binary search to locate the highest layer count that still passes a context stability test, then optionally runs a quick benchmark across all layers.
cd scripts
./glacierflow_autotune_gpu_layers- Stability test — runs
llama-benchy(viauvx) with a given context size (--pp) and checks whether the inference server can process the prompt without OOM or errors - Binary search — starts from the current
--gpu-layersvalue and searches up or down to find the flip point (highest passing layer) - Quick benchmark — sends 3 requests using the
c1.jsonpreset and averages the predicted TPS, stepping down one layer at a time
The script modifies --gpu-layers in the compose source file and restarts the inference server between each test. It requires the daemon to be running so it can manage the server lifecycle.
- The autotune script works on currently loaded preset and does not support llama's router mode
- The preset file must contain a
--gpu-layersargument in one line for the script to work
gf_gen_mtp_prob_presets_range generates a grid of inference presets from a prototype file for sweeping MTP (multi-token prediction) settings. It creates a 9×9 grid by varying:
--spec-draft-n-maxfrom1to9--spec-draft-p-minfrom0.1to0.9
Each combination is written as 000_testbed_mtp_<n-max>_prob_<p-min>.yml next to the prototype file (existing files are overwritten).
# With a prototype argument (bare filename or path)
cd scripts
./gf_gen_mtp_prob_presets_range preset_to_base_mtp_variations_on.yml
# Without an argument — prompts for the prototype file
# (default: preset_to_base_mtp_variations_on.yml)
cd scripts
./gf_gen_mtp_prob_presets_rangeA bare filename is resolved against data/shared/inference_presets/local; a path (containing /) is used as-is. The prototype must contain both --spec-draft-n-max and --spec-draft-p-min on non-comment lines, otherwise the script exits with an error.
The generated presets are meant to be benchmarked one by one (e.g. with glacierflow_benchmark, which is the recommended tool for MTP tuning) to find the best n-max / p-min combination for a given model.
GlacierFlow supports running stable-diffusion.cpp for image generation, replacing llama.cpp inference server. The daemon can automatically download and update the binaries on start.
- Enable in
.env— setGF_STABLE_DIFFUSION_ENABLE=1to have the daemon fetch/update stable-diffusion.cpp binaries automatically on each startup:
GF_STABLE_DIFFUSION_ENABLE=1The source URL is configurable via GF_STABLE_DIFFUSION_SOURCE in .env. The default points to a Vulkan build. Binaries are extracted into data/stable_diffusion/binaries/, and the current version is tracked in data/stable_diffusion/current_source.txt.
- Pick a preset — example Stable Diffusion presets live in
data/shared/inference_presets/examples/. Copy one to your local directory:
cp data/shared/inference_presets/examples/vulkan_8gb_stable_diffusion_realistic_stock_photo_v20.yml \
data/shared/inference_presets/local/Select it as usual via the preset switcher or Web UI.
- Restart the daemon — the binaries will be downloaded on next boot and the inference server will restart with the new preset.
| Preset | Model | Notes |
|---|---|---|
vulkan_8gb_stable_diffusion_realistic_stock_photo_v20.yml |
realisticStockPhoto v2.0 | SD 1.5 model, Vulkan + CPU offload |
vulkan_8gb_stable_diffusion_ideogram_4.yml |
ideogram-4 GGUF | Includes LLM VAE pipeline with Qwen3-VL, Vulkan + CPU offload |
When GF_STABLE_DIFFUSION_ENABLE=1, the daemon calls data/stable_diffusion/update_source.sh during startup. The script:
- Checks if the binary source URL has changed (compared against
current_source.txt) - Downloads and extracts the release archive into
binaries/ - Overwrites any existing installation
The preset YAML files use a Dockerfile at data/stable_diffusion/docker/Dockerfile that installs Vulkan drivers and runs /app/sd-server as entrypoint. The inference server port (8080) is shared.
GlacierFlow supports running audio.cpp for audio model inference, replacing llama.cpp inference server. The daemon can automatically clone and build the source on start.
- Enable in
.env— setGF_AUDIO_CPP_ENABLE=1to have the daemon clone/build audio.cpp automatically on each startup:
GF_AUDIO_CPP_ENABLE=1The source is cloned from https://github.com/0xShug0/audio.cpp.git into ~/.audio.cpp-src (configurable via GF_AUDIO_CPP_SRC_DIR). The build is CPU-heavy, so you may prefer to run data/audio_cpp/update_source.sh manually with GF_AUDIO_CPP_ENABLE=0. The current build version (git commit) is tracked in data/audio_cpp/last_build.txt — the build is skipped when the source is unchanged.
-
Place models — audio.cpp models (GGUF) go into the
AUDIO_CPPsubdirectory of your models directory (GF_MODELS_DIRECTORY/AUDIO_CPP/). Models are available at audio-cpp/audio.cpp-gguf. -
Pick a preset — the example audio.cpp preset lives in
data/shared/inference_presets/examples/. Copy it to your local directory:
cp data/shared/inference_presets/examples/vulkan_8gb_audio_cpp.yml \
data/shared/inference_presets/local/Select it as usual via the preset switcher or Web UI.
- Restart the daemon — the server will start with the new preset.
| Preset | Notes |
|---|---|
vulkan_8gb_audio_cpp.yml |
Vulkan backend, web UI enabled (--ui --ui-management), single loaded model |
When GF_AUDIO_CPP_ENABLE=1, the daemon calls data/audio_cpp/update_source.sh during startup. The script:
- Clones the audio.cpp repo if missing, then pulls the latest source
- Compares the current git commit against
last_build.txtand skips the build when unchanged - Builds the
audiocpp_cliandaudiocpp_serverbinaries with the Vulkan backend and copies them intodata/audio_cpp/build/bin/
The preset is built from a Dockerfile at data/audio_cpp/docker/Dockerfile (Ubuntu 26.04 with libgomp and Mesa Vulkan drivers) that runs /app/audiocpp_server as entrypoint. The preset mounts the build output to /app and the AUDIO_CPP models directory to /app/models. The inference server port (8080) is shared.
See the audio.cpp server example docs for server options.
The daemon tracks the server state in data/shared/inference_status.txt:
| Status | Meaning |
|---|---|
RUNNING |
Server is up and responding (HTTP 200) |
LOADING |
Server container started, model still loading |
STOPPED |
Server is down |
LAUNCHING |
Docker compose is pulling/starting |
AUTO_SWITCHING |
FIFO proxy is switching the backend between LLM and SD presets (automatic mode switching) |
LOCKED |
Server is locked (inference lock file present) |
NOT CONFIGURED |
No active config found |
DEGRADED |
Unexpected response |
GlacierFlow includes a Go-based FIFO (first-in-first-out) proxy that sits between clients and the llama.cpp inference server. It serializes /v1/chat/ requests so they are processed strictly one at a time, preventing SSE stream cut-offs when the model is hot-swapped mid-inference.
- Serialized routing:
/v1/chat/requests are queued and dispatched one-by-one to the backend - Parallel routing: All other routes (health checks, non-chat endpoints) are proxied in parallel via standard reverse proxy
- Auto-rebuild: The entrypoint detects source changes and rebuilds the Go binary automatically
- Restart loop: If the proxy crashes, it restarts after a 2-second delay
The proxy runs on port 8070 (container port 8080). It is started automatically by the daemon — no manual setup required.
If you prefer to bypass the proxy entirely, uncomment the ports section in docker/llama_cpp/compose.yml and point your clients directly at port 8080 (Or create preset with ports section override).
The proxy can record request and response pairs to JSON files for debugging and inspection. Enable it by setting GF_PROXY_DUMP_REQUESTS=1 in your .env file:
GF_PROXY_DUMP_REQUESTS=1When enabled, each request/response pair is dumped to docker/fifo_proxy/dumps/ as three files sharing a common <timestamp> prefix (format: YYYY_MM_DD_HH_MM_SS):
<timestamp>_request.json— the exact request body, unmodified<timestamp>_response.json— the full response body, pretty-printed when it is valid JSON, raw bytes otherwise (e.g. SSE event streams)<timestamp>_details.json— request method and path:
{
"requestMethod": "POST",
"requestPath": "/v1/chat/completions"
}The dump is written when the response completes or when the client disconnects. Dumped files are gitignored.
The proxy can automatically switch the backend between a llama.cpp (chat) preset and a stable-diffusion.cpp (image) preset. This lets a single client — e.g. Cherry Studio — use both chat and image generation through the same endpoint (port 8070), without manually switching presets via the daemon, CLI or Web UI.
Requests are classified by route:
/v1/chat/*→ LLM mode (llama.cpp preset)/v1/images/*→ SD mode (stable-diffusion.cpp preset)
When a request arrives in a mode different from the one currently loaded, the proxy:
- Waits until the backend is idle (
RUNNINGstatus) - Remembers the preset currently loaded for the mode it is switching away from, so it can be restored on the next switch back
- Writes the target preset name to
data/shared/inference_preset_name_target.txtand copiesdata/shared/inference_presets/local/<preset>.ymltodata/shared/inference_config.yml - Sets the status to
AUTO_SWITCHINGand waits for the daemon to restart the server and the status to return toRUNNING
The request that triggered the switch is held in the FIFO queue until the new model is ready. The proxy always starts in LLM mode.
- Enable in
.env:
GF_PROXY_AUTO_MODE=1
GF_DEFAULT_MODEL_LLM=vulkan_8gb_gemma4_e4b_q4
GF_DEFAULT_MODEL_SD=vulkan_8gb_stable_diffusion_z_image_turboGF_PROXY_AUTO_MODE— set to1to enable automatic mode switching (default0)GF_DEFAULT_MODEL_LLM— name of the llama.cpp preset (filename without.yml) to load for chat mode. The daemon also loads this preset on startup, so the backend starts in LLM modeGF_DEFAULT_MODEL_SD— name of the stable-diffusion.cpp preset to load for image generation mode
-
Place both presets in
data/shared/inference_presets/local/— the proxy copies the preset file from the local presets directory, so example presets alone are not enough. You need to setup i.e. width and height for z-image. -
Keep the daemon running — the daemon is what applies the config change and restarts the inference container. The proxy only coordinates via the shared status/preset files in
data/shared/(mounted into the proxy container at/glacierflow-shared).
- The first request after a mode switch waits for the new model to load (status
AUTO_SWITCHING), so expect a delay on that request - If
GF_DEFAULT_MODEL_LLM/GF_DEFAULT_MODEL_SDis empty, or the preset file is missing, the switch is skipped and the request is proxied to the backend in its current mode - Manual preset switching (CLI / Web UI) still works as usual — the proxy picks up the manually selected preset name the next time it switches back to that mode.
- Manual switching between sd and llm modes might get daemon confused but switching between i.e. gemma and qwen wis fine.
GlacierFlow can log inference performance metrics — fill TPS, gen TPS, draft acceptance rate, and total tokens — to per-preset log files and visualize them in the Web UI.
- Enable logging by setting
GF_LOG_STATS=1in your.envfile:
GF_LOG_STATS=1- Start the daemon. The daemon will write stats to
data/stats/<YYMMDD>_<preset_name>_<hash>.logfiles — one per day per preset.
Open the Stats Viewer from the Web UI toolbar link. The viewer loads the log files and renders them as an interactive Chart.js chart. Clear all button resets the chart view.
If preset is using llama's router mode you won't be able to recognize which model was actually called.
GlacierFlow includes a configurable llama.cpp embedding server for generating vector embeddings. It is disabled by default and can be enabled by creating an override config.
- Enable the server by creating
docker/llama_embeddings/compose.override.ymlwith your desired command:
services:
glacierflow-embeddings:
command: >
-hf ggml-org/embeddinggemma-300M-GGUF
--embeddings- Configure the port in your
.envfile (defaults to 8060):
GF_EMBEDDING_PORT=8060The embedding server starts automatically alongside the main daemon when the override config is present.
Once running, the embedding server exposes an OpenAI-compatible API at http://localhost:8060/v1/embeddings.
GlacierFlow ships with a Dockerized pi.dev development environment for AI-assisted code generation. It runs inside a full-featured dev container with all common languages and tools pre-installed.
- Configure the model provider in
data/pi_dev/config/agent/models.json:
{
"providers": {
"glacierflow-llamacpp": {
"baseUrl": "http://glacierflow-proxy:8080/v1",
"api": "openai-completions",
"apiKey": "none",
"models": [
{ "id": "glacierflow-coder-feather" },
{ "id": "glacierflow-coder-light" },
{ "id": "glacierflow-coder-default" },
{ "id": "glacierflow-coder-heavy" },
{ "id": "glacierflow-coder-thinking-feather", "reasoning": true, "thinkingLevelMap": { "off": null } },
{ "id": "glacierflow-coder-thinking-light", "reasoning": true, "thinkingLevelMap": { "off": null } },
{ "id": "glacierflow-coder-thinking-default", "reasoning": true, "thinkingLevelMap": { "off": null } },
{ "id": "glacierflow-coder-thinking-heavy", "reasoning": true, "thinkingLevelMap": { "off": null } }
]
}
}
}Note: By default this points through the FIFO proxy (
glacierflow-proxy) so chat requests are serialized. If you bypass the proxy, change the URL tohttp://glacierflow-llamacpp:8080/v1.
- Set your inference preset — the pi.dev container by default uses the model you configured with
glacierflow_inference_select_presetscript.
The start script tracks a default model (default: glacierflow-coder-thinking-default) and passes it to all pi invocations via --model. The current default is shown at the top of every menu, and you can change it with the [M] option (main and kick-off menus). The selection is persisted to ~/.default_model inside the container and restored on next start (unknown/stale values fall back to the built-in default).
The pi.dev container starts automatically with the daemon. You can access it in two ways:
Web Terminal (TTYD): Open https://localhost:7681 in your browser (or click "pi.dev httpd" from the Web UI toolbar). Log in with the credentials set via GF_HTTP_USER / GF_HTTP_PASSWORD (defaults: glacierflow / glacierflow). The terminal launches inside a persistent tmux session. Note: if using the default self-signed certificate, you'll need to accept the browser's security warning.
Management script: For start/stop/attach control:
cd scripts
./glacierflow_pi_code- Image: Alpine-based with bash, curl, ttyd, tmux, git, npm, and vim as basic requirements. Additional packages can be passed via
GF_PI_EXTRA_PACKAGES.env arg (space-separated list). - User: Runs as non-root (
pi_dev, UID configurable viaGF_USER_ID, defaults to 1000) - TTYD: Web terminal server on port 7681 with basic auth (configurable via
GF_PI_DEV_TTYD_USER/GF_PI_DEV_TTYD_PASSWORD) - Auto-update: On startup, the container checks for and applies pi.dev updates automatically. Extensions are not pre-installed but can be added manually — they survive image rebuilds.
- tmux: Persistent session (
pi_dev_task) with custom config mounted fromdata/pi_dev/tmux.conf - Volume mounts:
- Project root →
/pi_dev(read-only) - Config →
~/.pi(read-write) - Scripts →
/pi_dev_scripts(read-write, for builtin conversation-starter scripts) - tmux config →
~/.tmux.conf(read-only) - Workdir → configurable via
GF_PI_DEV_WORKDIRenv var (read-write)
- Project root →
GlacierFlow ships with a Dockerized Hermes agent environment for AI-driven task management, research, and kanban workflows. It runs inside an Ubuntu-based container with Hermes CLI, web dashboard, and gateway pre-installed.
- Auto-start with daemon — set
GF_HERMES_AUTOSTART=1in your.envfile to start the Hermes container automatically when the daemon launches:
GF_HERMES_AUTOSTART=1- Manual start — use the management script:
cd scripts
./glacierflow_hermesThe Hermes container provides multiple access points:
Web Terminal (TTYD): Open https://localhost:7682 in your browser (or click "hermes httpd" from the Web UI toolbar). Log in with the credentials set via GF_HTTP_USER / GF_HTTP_PASSWORD.
Hermes Dashboard: Open https://localhost:7683 in your browser (or click "hermes Web UI" from the Web UI toolbar) for the web-based Hermes dashboard.
To use your local llama.cpp inference server with Hermes:
- Attach to the container or use the TTYD terminal
- Run the configuration menu (
Ckey) orhermes setup - Use
http://glacierflow-proxy:8080/v1as the custom endpoint URL to route through the FIFO proxy
- Image: Ubuntu-based with bash, curl, ttyd, tmux, git, vim, python3, nodejs, npm, ripgrep, ffmpeg
- User: Runs as non-root (
ubuntu, UID configurable viaGF_USER_ID/GF_USER_GROUP_ID, defaults to 1000) - TTYD: Web terminal server on port 7682 with basic auth
- Dashboard: Web UI on port 7683
- tmux: Persistent session (
hermes_task) - Volume mounts:
- Hermes data →
data/hermes/(read-write, configurable viaGF_HERMES_WORKDIR)
- Hermes data →
GlacierFlow includes a code-server instance for web-based code editing. It shares the same workspace as pi.dev and is accessible through the Caddy reverse proxy.
- Auto-start with daemon — set
GF_VSCODE_AUTOSTART=1in your.envfile to start the VS Code container automatically when the daemon launches:
GF_VSCODE_AUTOSTART=1- Configure the version (optional) — by default the latest image is used. Pin a specific version with:
GF_VSCODE_VERSION=4.96.1- Configure the port (defaults to 7684):
GF_VSCODE_WEB_PORT=7684Access VS Code at https://localhost:7684 in your browser, or click "VS Code" from the Web UI toolbar.
- Image:
codercom/code-server(latest by default, configurable viaGF_VSCODE_VERSION) - User: PUID/PGID configurable via
GF_USER_ID/GF_USER_GROUP_ID - Healthcheck: Monitors HTTP availability on port 8080
- Volume mounts:
- Local data →
data/vscode/local/(user settings, extensions) - Config →
data/vscode/config/(code-server config) - Workdir → shared with pi.dev via
GF_PI_DEV_WORKDIR
- Local data →
- Caddy: Reverse proxied on port 7684 through the Caddy service
GlacierFlow itself is a collection of custom scripts — no license applies. Third-party software (llama.cpp, stable-diffusion.cpp, audio.cpp, Docker, pi.dev, llama-benchy, vscode and so on.) is subject to their respective licenses.