Este laboratorio ejecuta llama.cpp server en Docker y lo pone a prueba como si fuera la API de OpenAI. Es la parte de Docker de nuestra guía cómo instalar llama.cpp en Linux, macOS y Docker, llevada hasta la API que detallamos en llama-server como API compatible con OpenAI en Docker. Todo lo que ves en la terminal se ejecutó de verdad, en una máquina arm64 sin GPU.

Qué hace este laboratorio

Arrancas la imagen ghcr.io/ggml-org/llama.cpp:server-v0.5.0, la misma etiqueta que fija la guía de la API. Con -hf ggml-org/Qwen3.5-0.8B-GGUF:Q4_0 el contenedor descarga el modelo de Hugging Face a un volumen montado en /cache. Esperas a que /health devuelva {"status":"ok"} en un bucle con límite, y luego compruebas tres cosas: sin clave, /v1/models responde 401; con clave, lista el modelo; y /v1/chat/completions devuelve una respuesta con su bloque usage.

Por qué este modelo y no el de la guía de la API

La guía de la API usa Qwen3 1.7B en Q8_0, un GGUF de 1,8 GB. Para que el laboratorio se pueda repetir con poca descarga elegimos el modelo de la guía de instalación, Qwen3.5 0.8B, del repositorio ggml-org/Qwen3.5-0.8B-GGUF, en su cuantización Q4_0. Ese repositorio no tiene Q4_K_M, así que sin la etiqueta :Q4_0 el servidor bajaría el Q8_0, más pesado. Qwen3.5 razona antes de responder, y por eso la petición lleva "reasoning_effort": "none" y un max_tokens de 64. Si te interesa elegir otra cuantización, la tienes explicada en cuantización de modelos y llama.cpp en tu portátil.

La clave de API y el volumen de caché

La clave vive en un fichero con permisos 600 que el contenedor monta en solo lectura y lee con --api-key-file, una línea por clave y comentarios con #, según la referencia de opciones de llama-server. Es el mismo principio que en nuestro laboratorio de gestión de secretos en Docker: la clave no queda en la imagen ni en la línea de órdenes. El valor tu_clave_de_api es de prueba; en tu servidor usa una clave larga y aleatoria.

El modelo se guarda en un volumen con nombre, como describe la documentación de volúmenes de Docker. Por eso, al borrar el contenedor y crear otro con el mismo volumen, el servidor arranca sin volver a descargar el GGUF. Si quieres ver ese mecanismo con más detalle, mira el laboratorio sobre cómo persisten los datos en Docker con volúmenes y bind mounts.

Cómo usar esta API desde tu código

El cuerpo de la petición sigue el formato de la API de chat de OpenAI, así que el SDK oficial funciona apuntando base_url a http://127.0.0.1:8080/v1 con tu clave. El puerto se publica solo en 127.0.0.1; para exponerlo fuera, ponlo detrás de un proxy inverso con TLS. Las variantes de la imagen para GPU, como server-cuda y server-vulkan, están en la documentación de Docker de llama.cpp; aquí no las probamos porque la máquina no tiene GPU.

Preguntas frecuentes

¿Qué etiqueta de la imagen de llama.cpp uso en Docker?

Una etiqueta fijada como ghcr.io/ggml-org/llama.cpp:server-v0.5.0. La etiqueta server sin versión apunta a la compilación nocturna y cambia sola; con la versión fijada actualizas cuando tú decides, cambiando la etiqueta. En este laboratorio el binario de esa imagen informa de la compilación 11146 para Linux aarch64.

¿Hace falta una GPU para ejecutar llama-server en Docker?

No. La imagen server funciona solo con CPU, y este laboratorio se grabó en una máquina arm64 sin GPU con 4 hilos (-t 4). Un modelo de 0,8 B en Q4_0 responde a una pregunta corta en menos de un segundo. Para GPU existen las variantes server-cuda y server-vulkan de la misma imagen.

¿Por qué /health responde sin clave si arranqué llama-server con –api-key-file?

Porque llama-server deja /health abierto a propósito, para que un healthcheck de Docker o de un proxy inverso pueda comprobar el servicio sin conocer la clave. Todo lo demás, incluido /v1/models, devuelve un 401 con el mensaje Invalid API Key si falta la cabecera Authorization.

¿Dónde guarda el contenedor el modelo que descarga con -hf?

En la ruta que indica la variable LLAMA_CACHE, con la estructura de la caché de Hugging Face (models–usuario–repositorio). Si esa ruta es un volumen de Docker, el GGUF sobrevive a docker rm y el siguiente contenedor arranca sin volver a descargarlo.