Deploying Michelangelo AI UI
This guide covers how the Michelangelo AI UI fits into Michelangelo AI's Kubernetes deployments and how operators can adapt the Helm chart configuration for their environments.
Context
The Michelangelo AI UI is a React-based web application that provides a graphical interface for managing projects, pipelines, and monitoring pipeline runs in the Michelangelo AI ML platform.
Audience
- Platform operators looking to understand UI deployment requirements
- Kubernetes administrators adapting the sandbox for production use
- Teams wanting to customize UI configuration for their environment
Key Concepts
- Helm Chart: The
michelangelochart (helm/michelangelo) deploys the UI, Envoy, and API server together. See the Helm Chart guide - ConfigMap Injection: Runtime configuration mounted into containers without rebuilding images
- proto3 JSON: The UI sends and receives protobuf's JSON mapping as plain HTTP
POST /{service}/{method}requests - Envoy Proxy: Proxy running the gRPC-JSON transcoder, which converts those JSON requests into gRPC calls to the API server
- Template Customization: Adapting chart values for specific infrastructure requirements
Setup
Prerequisites
- Kubernetes cluster with Michelangelo AI API server deployed
- Access to Michelangelo AI UI container image from GitHub Container Registry
UI Deployment Requirements
1. Container Deployment
The chart deploys the UI container (ui.enabled: true by default) with runtime configuration mounted:
Image Selection:
Available from GitHub Container Registry at ghcr.io/michelangelo-ai/ui:
- Chart default: the chart's
appVersion(images.ui.tag: "") - Latest development:
:main(whatvalues-k3d.yamluses) - Specific commit:
:sha-<short-sha> - Pull command:
docker pull ghcr.io/michelangelo-ai/ui:your-chosen-tag
Override with images.ui.repository and images.ui.tag in your Helm values.
Container Requirements:
- Configuration:
config.jsonmounted at/usr/share/nginx/html/config.json(the chart does this from a ConfigMap) - Port: Container serves on port 80
The UI needs to know how to connect to your Michelangelo AI API server. This is configured through ui.apiBaseUrl, which the chart renders into the config.json ConfigMap:
- API endpoint: Browser-reachable URL of the Envoy proxy (not the API server itself)
ui:
apiBaseUrl: "https://michelangelo.example.com/api"
The resulting config.json is:
{
"apiBaseUrl": "https://michelangelo.example.com/api"
}
ui.apiBaseUrl is required when ui.enabled=true. If you leave it empty and enable envoy.ingress with at least one host, the chart derives it from the first Envoy ingress host and path.
2. Envoy Proxy Setup
The UI communicates with the API server through Envoy (envoy.enabled: true by default), which requires:
Port Configuration:
- Envoy listens on port 8081 (
envoy.port) - The API server listens on its gRPC port, 15566 by default (
apiserver.port) - The UI makes requests to Envoy, not directly to the API server
CORS Configuration:
Envoy must allow the UI's origin. Set envoy.corsOrigins to a regular expression matching the scheme, host, and port of the UI (escape literal dots). It is required on every Ingress install:
envoy:
corsOrigins: "https://michelangelo\\.example\\.com"
For local development use "http://localhost:[0-9]+". If unset, the chart allows any origin (.*).
Transcoding and Backend Routing:
The chart renders an Envoy config (helm/michelangelo/templates/core/envoy-configmap.yaml) with a single route to the michelangelo-apiserver cluster, whose address and port are filled in from the API server Service and apiserver.port. The envoy.filters.http.grpc_json_transcoder filter handles the allowlisted services listed in helm/michelangelo/files/transcoder-services.json and the descriptor set mounted from helm/michelangelo/files/descriptors.pb. Its print_options set always_print_enums_as_ints: false and always_print_primitive_fields: true, which the UI relies on, so keep them if you maintain your own Envoy config.
If you maintain your own Envoy config rather than using the chart, the route cluster: name must match the cluster definition name::
# In the route configuration:
route:
cluster: michelangelo-apiserver # This name must match below
# In the clusters section:
clusters:
- name: michelangelo-apiserver # Must match the route cluster name
load_assignment:
cluster_name: michelangelo-apiserver
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: michelangelo-apiserver # Your API server service name
port_value: 15566 # Your API server gRPC port
Common Issue: Mismatched cluster names between route configuration and cluster definition will cause "no healthy upstream" errors.
3. Network Connectivity
Ensure network paths are configured:
- Browser → Envoy proxy (port 8081, or your Envoy ingress)
- Envoy proxy → API server (port 15566)
- External access → UI container (Service port 80 by default,
ui.service.port)
External Access Details: The UI container serves the React application on port 80 (standard HTTP). Users need to be able to reach this port through your Kubernetes networking setup. Common approaches:
- NodePort: Set
ui.service.type: NodePortandui.service.nodePort(sandbox default is30011, with Envoy on30010) - Ingress: Domain-based routing via
ui.ingressandenvoy.ingress(e.g.,michelangelo.yourcompany.com) - Port forwarding: Development access via
kubectl port-forward
The UI serves static files (HTML, JS, CSS) from nginx, so it's a standard web server that needs HTTP access from users' browsers.
Reference Implementation
For a complete working example, see the Sandbox Guide, which installs the chart with helm/michelangelo/values-k3d.yaml and has all components configured and connected.
Verification
Once deployed, verify the connection chain:
- UI loads: Access the UI in browser
- Config loads: Check browser dev tools for
/config.json - API connectivity: Verify API calls succeed (no CORS errors in browser console)
Troubleshooting
Common Issues
Envoy "no healthy upstream" errors:
- Cause: Cluster name mismatch between route configuration and cluster definition, or the API server Service/port is unreachable from Envoy
- Fix: Ensure the
cluster:field in routes matches thename:field in clusters section, and that the cluster address and port match the API server Service andapiserver.port - Check: Envoy logs will show "no healthy upstream for cluster 'cluster-name'"
CORS errors in browser:
- Verify
envoy.corsOriginsmatches your UI's origin (scheme, host, and port) - Check browser dev tools → Network tab for failed requests
- Remember the value is a regular expression, so escape literal dots as
\.
FAQ
Q: Can I use a custom domain?
A: Yes, configure ui.ingress and envoy.ingress, and update envoy.corsOrigins to match.
Q: What if my API server uses different ports?
A: Set apiserver.port; the chart updates the Envoy cluster to match. Update ui.apiBaseUrl only if you change how Envoy is exposed (envoy.port or the Envoy ingress).