AI Devcontainer — Architecture & Setup
Overview
This devcontainer provides a self-contained, GPU-accelerated AI development environment running inside a DevPod workspace. It serves a full local LLM stack accessible publicly via a Cloudflare tunnel at https://ai.uclab.dev.
Host Requirements
| Requirement | Details |
|---|---|
| Container runtime | Docker with nvidia-container-toolkit |
| GPU passthrough | CDI (nvidia.com/gpu=all) |
| DevPod workspace | Workspace ID: ai |
| Host directories | ~/.ollama (model storage), ~/.cloudflared (tunnel credentials) |
File Structure
Docker Image
Base: nvidia/cuda:12.8.0-runtime-ubuntu24.04
The image is built from .devcontainer/Dockerfile with the repo root as context.
Build stages (in order)
| Step | What happens |
|---|---|
Copy mise, uv |
Pulled from upstream images (jdxcode/mise, astral-sh/uv) |
apt-get |
Installs: sudo zstd libatomic1 supervisor nvtop git curl vim zsh |
| Ollama | Installed system-wide via https://ollama.com/install.sh |
| cloudflared | Installed via Cloudflare’s official apt repo |
| open-webui | Installed system-wide via uv pip install --system --break-system-packages |
| Supervisor configs | supervisord/*.conf → /etc/supervisor/conf.d/ |
pull-models script |
Copied to /usr/local/bin/pull-models, made executable |
ubuntu sudoers |
ubuntu ALL=(ALL) NOPASSWD:ALL — passwordless sudo |
Switch to ubuntu |
All subsequent steps run as non-root |
| mise tools | mise.toml baked in; Node LTS, Python 3.13, uv installed |
| Claude Code | npm install -g @anthropic-ai/claude-code via mise-managed Node |
| Shell activation | mise activate added to .bashrc and .zshrc |
Developer tools (via mise)
Defined in /workspaces/ai/mise.toml:
Tools are installed into /home/ubuntu/.local/share/mise/ and shimmed onto PATH via /etc/profile.d/mise.sh.
Container Configuration (devcontainer.json)
GPU passthrough
Requires nvidia-container-toolkit on the host with CDI configured. The label=disable flag is needed for SELinux compatibility.
Environment variables
Set at the container level (available to all processes):
| Variable | Value | Purpose |
|---|---|---|
NVIDIA_VISIBLE_DEVICES |
all |
Expose all GPUs |
NVIDIA_DRIVER_CAPABILITIES |
all |
Full GPU capability set |
OLLAMA_KEEP_ALIVE |
10m |
Keep model loaded in VRAM for 10 min after last use |
OLLAMA_MAX_LOADED_MODELS |
1 |
Max 1 model in VRAM at a time (8 GB card) |
OLLAMA_NUM_PARALLEL |
1 |
Single inference thread |
OLLAMA_BASE_URL |
http://127.0.0.1:11434 |
Used by Open WebUI to reach Ollama |
API keys passed from the host:
| Variable | Source |
|---|---|
ANTHROPIC_API_KEY |
${localEnv:ANTHROPIC_API_KEY} |
MISE_GITHUB_TOKEN |
${localEnv:MISE_GITHUB_TOKEN} |
Persistent mounts
Both directories live on the host and are bind-mounted into the container, so they survive full container rebuilds:
| Host path | Container path | Contents |
|---|---|---|
~/.ollama |
/home/ubuntu/.ollama |
Downloaded model weights (~27 GB) |
~/.cloudflared |
/home/ubuntu/.cloudflared |
Tunnel credentials and config |
Port forwarding
| Port | Service | Behaviour on attach |
|---|---|---|
8080 |
Open WebUI | Opens in browser automatically |
11434 |
Ollama API | Forwarded silently |
Port 8080 is also bound on the host via -p 8080:8080 in runArgs.
Startup sequence (postStartCommand)
supervisordstarts as a daemon — launches Ollama, Open WebUI, and cloudflared- A background job polls Ollama until it responds on port 11434
- Once Ollama is ready,
pull-modelsruns and downloads any missing models postStartCommandreturns immediately so the IDE is not blocked
Process Management (Supervisor)
The system supervisor (/etc/supervisor/supervisord.conf) is the system default Debian config. It includes all files from /etc/supervisor/conf.d/*.conf, which are baked into the image from .devcontainer/supervisord/.
All three programs run as user ubuntu with HOME="/home/ubuntu" explicitly set (supervisor does not inherit HOME when switching users).
Priority order
ollama
OLLAMA_HOST=0.0.0.0:11434 makes Ollama listen on all interfaces (required for DevPod port forwarding to work).
open-webui
DATA_DIR must point to a writable path — open-webui was installed as root so its package directory is not writable by ubuntu.
RAG uses nomic-embed-text via Ollama for embeddings. Web search uses DuckDuckGo (no API key required).
cloudflared
The tunnel ID and credentials are static. Configuration is read from the bind-mounted /home/ubuntu/.cloudflared/config.yml:
All traffic on ai.uclab.dev is forwarded to Open WebUI on port 8080.
Model Management (pull-models)
Location in image: /usr/local/bin/pull-models
The script is idempotent — it checks ollama list before pulling and skips models already present. Since ~/.ollama is a persistent host mount, models are only downloaded once regardless of how many times the container is rebuilt.
Configured models
| Model | Size | Purpose |
|---|---|---|
llama3.1:8b |
4.9 GB | General-purpose chat |
qwen2.5:7b |
4.7 GB | General-purpose, strong reasoning |
deepseek-coder-v2:16b-lite-instruct-q4_K_M |
10 GB | Code generation |
mistral-nemo |
7.1 GB | Fast general-purpose |
Total: ~27 GB. Stored on a 1.8 TB volume (currently 10% used).
Note: The RTX 5060 has 8 GB VRAM. Only one model loads at a time (
OLLAMA_MAX_LOADED_MODELS=1). Models larger than VRAM will run partially on CPU.
Logs
All service logs are written to /var/log/ inside the container:
| File | Service |
|---|---|
/var/log/ollama.log / .err |
Ollama stdout / stderr |
/var/log/open-webui.log / .err |
Open WebUI stdout / stderr |
/var/log/cloudflared.log / .err |
Cloudflared stdout / stderr |
/var/log/supervisor/supervisord.log |
Supervisor daemon log |
Quick check:
Rebuild Checklist
Everything needed to fully recreate the environment is either in the image or on the host:
| What | Where | Survives rebuild? |
|---|---|---|
| Container config | .devcontainer/ in repo |
Yes — source of truth |
| Model weights | ~/.ollama (host mount) |
Yes |
| Cloudflare credentials | ~/.cloudflared (host mount) |
Yes |
| Open WebUI data (users, chats) | ~/.local/share/open-webui (inside container) |
No — lost on rebuild |
| mise tool cache | Inside image (baked at build time) | Rebuilt from mise.toml |
Warning: Open WebUI user accounts and chat history live inside the container at
/home/ubuntu/.local/share/open-webui. To persist this across rebuilds, add a host mount for that path indevcontainer.json.
