Ollama app
Ollama is a lightweight runtime for serving open large language models (LLMs) locally, with a built-in OpenAI-compatible API. It is a good fit for smaller models and quick local-style serving, and complements the higher-throughput vLLM and SGLang integrations.
Unlike vLLM and SGLang, Ollama has no dedicated *AppEnvironment plugin. Instead, you serve it with the generic
AppEnvironment: an image that installs Ollama plus a small entrypoint that launches ollama serve and pulls the model on startup.
Installation
Ollama needs no Flyte plugin — it is installed into the app image. Start from the default Flyte base image and add the Ollama binary with a build command:
# Install Ollama on top of the default Flyte base image. `install.sh` drops the
# `ollama` binary into /usr/local/bin (no systemd is needed inside a container).
image = (
flyte.Image.from_debian_base(python_version=(3, 12))
.with_apt_packages("curl")
.with_commands(["curl -fsSL https://ollama.com/install.sh | sh"])
)
The install.sh script drops the ollama binary into /usr/local/bin; no systemd service is needed inside a container.
Basic Ollama app
Define the app with a GPU-backed AppEnvironment. The args run the script’s --server entrypoint, which starts Ollama and pulls the model:
ollama_app = flyte.app.AppEnvironment(
name="ollama-app",
image=image,
args=["python", file_name, "--server"],
port=PORT,
resources=flyte.Resources(
cpu="4",
memory="16Gi",
gpu="L40s:1", # GPU accelerates inference; omit to run small models on CPU
disk="20Gi",
),
scaling=flyte.app.Scaling(
replicas=(0, 1),
scaledown_after=300, # Scale down after 5 minutes of inactivity
),
requires_auth=False,
)
The --server entrypoint binds Ollama to the app port, waits for it to be ready, and pulls the model so the OpenAI-compatible endpoint can serve it:
def serve() -> None:
"""Start `ollama serve`, wait for it, pull the model, then block."""
# Bind to all interfaces so the platform can route to the server.
server = subprocess.Popen(
["ollama", "serve"],
env={**os.environ, "OLLAMA_HOST": f"0.0.0.0:{PORT}"},
)
# Wait for the server to accept connections.
for _ in range(60):
try:
urllib.request.urlopen(f"http://127.0.0.1:{PORT}/api/version", timeout=2)
break
except Exception:
time.sleep(1)
# Pull the model so the OpenAI-compatible endpoint can serve it.
subprocess.run(
["ollama", "pull", MODEL],
env={**os.environ, "OLLAMA_HOST": f"127.0.0.1:{PORT}"},
check=True,
)
print(f"Ollama serving '{MODEL}' on port {PORT}")
server.wait()
Deploy it with flyte.serve:
if __name__ == "__main__":
if "--server" in sys.argv:
serve()
else:
flyte.init_from_config(root_dir=Path(__file__).parent)
app = flyte.serve(ollama_app)
print(f"Deployed Ollama app: {app.url}")
Choosing a model
MODEL can be any tag from the
Ollama library — for example qwen3:0.6b, llama3.2:1b, or gemma3:1b. Larger models need more GPU memory and disk; size the resources accordingly.
Small models run comfortably on CPU. To run without a GPU, drop the gpu field from resources. A GPU is recommended for larger models or higher throughput.
Using the OpenAI-compatible API
Once deployed, the Ollama app exposes an OpenAI-compatible API. Call it exactly like the vLLM or SGLang apps:
from openai import OpenAI
client = OpenAI(
base_url="https://your-app-url/v1", # Ollama endpoint
api_key="ollama", # Ollama ignores the key; any non-empty string works
)
response = client.chat.completions.create(
model="qwen3:0.6b", # Your MODEL tag
messages=[
{"role": "user", "content": "Hello, how are you?"}
],
)
print(response.choices[0].message.content)Chat UI with Streamlit
To put a browser UI in front of the model, run Ollama internally and expose a Streamlit chat interface instead of the raw API. Only the Streamlit port is exposed; the browser talks to Streamlit, and Streamlit talks to Ollama over localhost:
image = (
flyte.Image.from_debian_base(python_version=(3, 12))
.with_apt_packages("curl")
.with_commands(["curl -fsSL https://ollama.com/install.sh | sh"])
.with_pip_packages("streamlit==1.41.1", "openai")
)
app_env = flyte.app.AppEnvironment(
name="ollama-streamlit",
image=image,
args=["python", file_name, "--server"],
port=APP_PORT,
resources=flyte.Resources(cpu="4", memory="16Gi", gpu="L40s:1", disk="20Gi"),
scaling=flyte.app.Scaling(replicas=(0, 1), scaledown_after=300),
requires_auth=False,
)
The UI is a standard Streamlit chat app that points its OpenAI client at the local Ollama server:
def render_ui() -> None:
"""The Streamlit chat UI. Talks to the local Ollama OpenAI-compatible API."""
import streamlit as st
from openai import OpenAI
st.set_page_config(page_title="Ollama Chat", page_icon="🦙")
st.title("🦙 Ollama Chat")
client = OpenAI(base_url=f"http://127.0.0.1:{OLLAMA_PORT}/v1", api_key="ollama")
if "messages" not in st.session_state:
st.session_state.messages = []
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
if prompt := st.chat_input("Ask something..."):
st.session_state.messages.append({"role": "user", "content": prompt})
st.chat_message("user").write(prompt)
response = client.chat.completions.create(
model=MODEL, messages=st.session_state.messages
)
answer = response.choices[0].message.content
st.session_state.messages.append({"role": "assistant", "content": answer})
st.chat_message("assistant").write(answer)
A helper starts Ollama in the background and pulls the model:
def start_ollama() -> None:
"""Launch `ollama serve` in the background and pull the model."""
subprocess.Popen(
["ollama", "serve"],
env={**os.environ, "OLLAMA_HOST": f"0.0.0.0:{OLLAMA_PORT}"},
)
for _ in range(60):
try:
urllib.request.urlopen(f"http://127.0.0.1:{OLLAMA_PORT}/api/version", timeout=2)
break
except Exception:
time.sleep(1)
subprocess.run(
["ollama", "pull", MODEL],
env={**os.environ, "OLLAMA_HOST": f"127.0.0.1:{OLLAMA_PORT}"},
check=True,
)
The container entrypoint runs that helper, then hands the exposed port to Streamlit:
if __name__ == "__main__":
if "--ui" in sys.argv:
# Re-entry: Streamlit runs this file to render the UI.
render_ui()
elif "--server" in sys.argv:
# Container entrypoint: start Ollama, then hand the port to Streamlit.
start_ollama()
subprocess.run(
[
"streamlit", "run", file_name,
"--server.port", str(APP_PORT),
"--server.address", "0.0.0.0",
"--", "--ui",
],
check=True,
)
else:
flyte.init_from_config(root_dir=Path(__file__).parent)
app = flyte.serve(app_env)
print(f"Deployed app: {app.url}")
Authentication
The generic AppEnvironment uses Union’s platform-level authentication. Leave requires_auth=True (the default) to require an authenticated caller, or set requires_auth=False for a public endpoint. Ollama has no built-in API-key argument of its own, so prefer platform auth over exposing a public endpoint. For app-managed authentication (verifying a Bearer token yourself with a Flyte secret), see
Secret-based authentication.
Calling an authenticated app
With requires_auth=True (the default), callers authenticate with a Union API key. Create one with the CLI:
flyte create api-key --name ollama-app-keyThis prints an export FLYTE_API_KEY="..." command. Pass the key in the Authorization: Bearer header when you invoke the endpoint:
curl -X POST "https://your-app-url/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $FLYTE_API_KEY" \
-d '{
"model": "qwen3:0.6b",
"messages": [{"role": "user", "content": "Hello, how are you?"}]
}'When calling through the OpenAI client, pass the same key as api_key — it becomes the Authorization: Bearer token (Ollama itself ignores the value). See
Using API keys with Union apps for details.
Autoscaling
Ollama apps work well with scale-to-zero, so an idle model server costs nothing:
app_env = flyte.app.AppEnvironment(
name="autoscaling-ollama-app",
image=image,
args=["python", "basic_ollama.py", "--server"],
port=8080,
resources=flyte.Resources(cpu="4", memory="16Gi", gpu="L40s:1", disk="20Gi"),
scaling=flyte.app.Scaling(
replicas=(0, 1), # Scale to zero when idle
scaledown_after=600, # 10 minutes idle before scaling down
),
requires_auth=True,
)Because the model is pulled at startup, a larger scaledown_after avoids re-pulling on every cold start.
Best practices
- Right-size resources: Match GPU memory and disk to the model. Small models run on CPU; drop the
gpufield to save cost. - Bake big models into the image: For faster, more reproducible cold starts,
ollama pullthe model in a build command instead of at startup. - Use scale-to-zero: Set an appropriate
scaledown_afterto balance cost against cold-start latency. - Prefer platform auth: Ollama has no native API-key auth, so rely on
requires_authrather than exposing a public endpoint. - Pick the right runtime: Use Ollama for lightweight or local-style serving; reach for vLLM or SGLang for high-throughput production inference.
Troubleshooting
Model pull fails or times out:
- Verify the
MODELtag exists in the Ollama library - Increase
diskinresourcesfor larger models - Review container logs for the
ollama pulloutput
Server not reachable:
- Confirm Ollama is bound to
0.0.0.0on the app port viaOLLAMA_HOST - Check that the app
portmatches the port Ollama serves on
Slow first response:
- The model is pulled on startup; use a larger
scaledown_afteror bake the model into the image - Use a smaller model, or add a GPU for faster inference