11# Running llms-py in Docker
22
33The Docker image bundles llms.py with everything its extensions need — Python,
4- ` bun ` , and the .NET SDK — so nothing has to be installed on the host beyond
5- Docker itself.
4+ ` bun ` , the .NET SDK and ` typst ` — so nothing has to be installed on the host
5+ beyond Docker itself.
66
77- [ Quick start] ( #quick-start ) — the one-line installer
88- [ What the installer does] ( #what-the-installer-does )
@@ -290,6 +290,137 @@ It exits non-zero if any check fails, so it works as a release gate. If a
290290provider API key happens to be in your environment it also runs a live
291291` llms --check ` against that provider.
292292
293+ ## What's in the image
294+
295+ | Tool | Used for |
296+ | --- | --- |
297+ | Python 3.11 + llms-py | the CLI and server |
298+ | ` bun ` / ` bunx ` | JavaScript extensions and tools |
299+ | .NET SDK 10 | running C# code |
300+ | ` typst ` | the PDF Studio extension (` .typ ` templates → PDF) |
301+ | ` ffmpeg ` | audio conversion for voice input |
302+ | ` git ` | installing extensions from a repo |
303+
304+ Each is verified at build time, so a broken image fails to build rather than
305+ failing on your first request.
306+
307+ ## Voice input
308+
309+ The microphone lives in the browser, not the container — the chat UI records
310+ audio and POSTs it to ` /transcribe ` , so no device passthrough is needed. The
311+ container's only job is turning that audio into text.
312+
313+ The voice extension's ` api ` mode needs nothing installed, so it works in the
314+ container out of the box. Add a key and restart:
315+
316+ ``` bash
317+ echo ' GROQ_API_KEY=gsk_...' >> ~ /.llms/.env
318+ llms restart
319+ ```
320+
321+ Configure the provider and model under ` defaults ` in ` ~/.llms/llms.json ` :
322+
323+ ``` json
324+ {
325+ "defaults" : {
326+ "voice" : {
327+ "provider" : " groq" ,
328+ "model" : " whisper-large-v3" ,
329+ "language" : " en"
330+ }
331+ }
332+ }
333+ ```
334+
335+ Or point it at a local speech-to-text server — no key required. From inside the
336+ container the host is ` host.docker.internal ` :
337+
338+ ``` json
339+ {
340+ "defaults" : {
341+ "voice" : {
342+ "url" : " http://host.docker.internal:8001/v1/audio/transcriptions" ,
343+ "model" : " Systran/faster-whisper-small"
344+ }
345+ }
346+ }
347+ ```
348+
349+ This is the same configuration llms.py uses everywhere, not something
350+ Docker-specific — see [ Voice Input] ( https://llmspy.org/docs/features/voice-input )
351+ for every setting, the ` LLMS_TRANSCRIBE_* ` environment overrides, and the other
352+ modes.
353+
354+ Check which mode was selected:
355+
356+ ``` bash
357+ llms restart --verbose && llms logs | grep -i voice
358+ ```
359+
360+ ```
361+ Using api for voice: groq [llms.json] model=whisper-large-v3 [llms.json]
362+ ```
363+
364+ The image also ships ` ffmpeg ` , which the ` voxtype ` and ` transcribe ` modes need
365+ to convert the browser's webm recording. ` voxtype ` requires a graphical desktop
366+ session so it never applies in a container; ` transcribe ` is available if you
367+ mount your own script at ` /usr/local/bin/transcribe ` .
368+
369+ ### The microphone button is missing
370+
371+ Browsers only expose ` getUserMedia ` in a ** secure context** : HTTPS, or
372+ ` http://localhost ` / ` http://127.0.0.1 ` . The default ` llms up ` binds to
373+ ` 127.0.0.1 ` , so it works.
374+
375+ If you set ` LLMS_BIND=0.0.0.0 ` and browse to ` http://192.168.x.x:8000 ` , the
376+ browser silently withholds the microphone API and no button appears — nothing to
377+ do with Docker or your configuration. Reach it over an SSH tunnel
378+ (` ssh -L 8000:localhost:8000 host ` ) or put it behind a TLS-terminating reverse
379+ proxy.
380+
381+ ### Logging
382+
383+ ` llms ` reads two environment variables, both off by default:
384+
385+ | Variable | Effect |
386+ | --- | --- |
387+ | ` VERBOSE=1 ` | request/response logging (same as ` --verbose ` ) |
388+ | ` DEBUG=1 ` | debug logging |
389+
390+ The ` llms ` command turns them into container env vars for you:
391+
392+ ``` bash
393+ llms up --verbose # request logging
394+ llms up --debug # verbose + debug
395+ llms up --debug -f # ...and follow the logs
396+ llms restart --debug # turn it on for a running server
397+ llms --debug ls # one-shot commands too
398+ llms logs # last 200 lines
399+ llms logs -f # follow
400+ llms status # shows the active log level
401+ ```
402+
403+ To make it permanent, set ` LLMS_VERBOSE=1 ` or ` LLMS_DEBUG=1 ` in ` ~/.llms/config ` .
404+
405+ Running the image directly, pass them as normal env vars:
406+
407+ ``` bash
408+ docker run -e VERBOSE=1 -e DEBUG=1 -p 8000:8000 \
409+ -v ~ /.llms:/home/llms/.llms ghcr.io/servicestack/llms:latest
410+ ```
411+
412+ ### Passing your own environment variables
413+
414+ Everything in ` ~/.llms/.env ` is passed into the container, not just API keys, so
415+ it doubles as a place for any setting the container should see:
416+
417+ ``` bash
418+ echo ' TZ=Australia/Perth' >> ~ /.llms/.env
419+ llms restart
420+ ```
421+
422+ ` llms setup ` preserves anything there that isn't a provider API key.
423+
293424## Troubleshooting
294425
295426** ` docker: command not found ` / daemon not running**
@@ -301,6 +432,35 @@ The image runs as UID 1000. If your UID is different, the installer detects it
301432and adds ` --user $(id -u):$(id -g) ` — stored as ` LLMS_DOCKER_USER_ARGS ` in
302433` ~/.llms/config ` . If you're running Docker by hand, add that flag yourself.
303434
435+ ** Voice input isn't working**
436+ Check which mode the extension picked:
437+
438+ ``` bash
439+ llms restart --verbose && llms logs | grep -i voice
440+ ```
441+
442+ ` Cannot use api - no voice provider configured ` means no key and no ` voice `
443+ section — add one. If no microphone button appears at all, see the secure-context
444+ note above.
445+
446+ ** PDF Studio isn't available**
447+ It disables itself when ` typst ` isn't on PATH. Check the image has it:
448+
449+ ``` bash
450+ llms shell -c ' typst --version'
451+ ```
452+
453+ If it's missing you're on an image built before typst was added — run
454+ ` llms update ` .
455+
456+ ** C# code fails with "Couldn't find a valid ICU package"**
457+ An old image without ` libicu ` . Run ` llms update ` . As a stopgap you can disable
458+ globalization instead:
459+
460+ ``` bash
461+ echo ' DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1' >> ~ /.llms/.env && llms restart
462+ ```
463+
304464** A provider is enabled but its models don't appear**
305465Check the key is actually reaching the container:
306466
0 commit comments