This guide covers deploying Starmap as a containerized HTTP server for production environments.
- Overview
- Quick Start
- Container Architecture
- Docker Compose
- Kubernetes Deployment
- Environment Variables
- Security Best Practices
- Health Checks
- Monitoring
- Troubleshooting
Starmap container images are:
- Minimal: Built with ko on Chainguard's static base (~2MB)
- Secure: Zero CVEs, no shell, no package manager
- Fast: Direct Go compilation, no multi-stage Docker builds
- Multi-platform: Native support for linux/amd64 and linux/arm64
Container Registry: ghcr.io/agentstation/starmap
# Basic server (embedded catalog only)
docker run -p 8080:8080 \
ghcr.io/agentstation/starmap:latest \
serve --host 0.0.0.0
# With API keys for live data
docker run -p 8080:8080 \
-e OPENAI_API_KEY=sk-... \
-e ANTHROPIC_API_KEY=sk-ant-... \
ghcr.io/agentstation/starmap:latest \
serve --host 0.0.0.0
# With persistent storage
docker run -p 8080:8080 \
-v starmap-data:/home/nonroot/.starmap \
ghcr.io/agentstation/starmap:latest \
serve --host 0.0.0.0
# Test the server
curl http://localhost:8080/api/v1/health# Latest stable
docker pull ghcr.io/agentstation/starmap:latest
# Specific version (recommended for production)
docker pull ghcr.io/agentstation/starmap:v0.0.17
docker pull ghcr.io/agentstation/starmap:0.0.17Starmap uses cgr.dev/chainguard/static:latest which provides:
- Size: ~2MB (vs 5MB for Alpine, 124MB for Debian)
- Security: Zero CVEs when scanned with grype/trivy
- Contents: CA certificates, timezone data, /etc/passwd with nonroot user
- No: Shell, package manager, utilities
Ko builds container images directly from Go source:
- No Dockerfile needed
- No Docker daemon required during build
- Native multi-platform support via Go cross-compilation
- Automatic SBOM generation (SPDX format)
- Reproducible builds with timestamp control
# Inspect image labels
docker inspect ghcr.io/agentstation/starmap:latest | jq '.[0].Config.Labels'
# View SBOM
docker pull ghcr.io/agentstation/starmap:latest
cosign verify-attestation --type spdx \
ghcr.io/agentstation/starmap:latest- Copy environment template:
cp .env.example .env- Edit .env with your configuration:
# .env
HTTP_HOST=0.0.0.0
HTTP_PORT=8080
# Optional: Provider API keys
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...- Start the services:
docker-compose up -d- View logs:
docker-compose logs -f starmap- Stop services:
docker-compose downFor production, enhance the provided docker-compose.yml:
version: '3.9'
services:
starmap:
image: ghcr.io/agentstation/starmap:v0.0.17 # Pin version
container_name: starmap-server
ports:
- "8080:8080"
env_file:
- .env
volumes:
- starmap-data:/home/nonroot/.starmap
- ./secrets/gcp-key.json:/secrets/gcp-key.json:ro # If using GCP
healthcheck:
test: ["CMD", "/ko-app/starmap", "version"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
restart: unless-stopped
# Security hardening
user: "65532:65532"
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
# Resource limits
deploy:
resources:
limits:
cpus: '2'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
starmap-data:
driver: local
networks:
default:
driver: bridge# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: starmap
namespace: default
labels:
app: starmap
spec:
replicas: 3
selector:
matchLabels:
app: starmap
template:
metadata:
labels:
app: starmap
spec:
containers:
- name: starmap
image: ghcr.io/agentstation/starmap:v0.0.17
imagePullPolicy: IfNotPresent
command: ["/ko-app/starmap"]
args: ["serve", "--host", "0.0.0.0", "--port", "8080"]
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: HTTP_HOST
value: "0.0.0.0"
- name: HTTP_PORT
value: "8080"
# Optional: Provider API keys from secrets
envFrom:
- secretRef:
name: starmap-api-keys
optional: true
# Health checks
livenessProbe:
exec:
command: ["/ko-app/starmap", "version"]
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 2
# Resources
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# Security context
securityContext:
runAsUser: 65532
runAsGroup: 65532
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# Volume mounts
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /home/nonroot/.starmap
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
# Pod security
securityContext:
fsGroup: 65532
seccompProfile:
type: RuntimeDefault# service.yaml
apiVersion: v1
kind: Service
metadata:
name: starmap
namespace: default
labels:
app: starmap
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
selector:
app: starmap# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: starmap
namespace: default
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- starmap.example.com
secretName: starmap-tls
rules:
- host: starmap.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: starmap
port:
number: 8080# configmap.yaml
apiVersion: v1
kind: Secret
metadata:
name: starmap-api-keys
namespace: default
type: Opaque
stringData:
OPENAI_API_KEY: "sk-..."
ANTHROPIC_API_KEY: "sk-ant-..."
GOOGLE_API_KEY: "..."# Create namespace (optional)
kubectl create namespace starmap
# Apply resources
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
# Check deployment
kubectl get pods -n starmap
kubectl logs -f deployment/starmap -n starmap
# Test service
kubectl port-forward svc/starmap 8080:8080 -n starmap
curl http://localhost:8080/api/v1/health| Variable | Default | Description |
|---|---|---|
HTTP_HOST |
localhost |
Bind address (use 0.0.0.0 for containers) |
HTTP_PORT |
8080 |
Server port |
LOG_LEVEL |
info |
Log level: trace, debug, info, warn, error |
| Variable | Provider | Description |
|---|---|---|
OPENAI_API_KEY |
OpenAI | API key for OpenAI models |
ANTHROPIC_API_KEY |
Anthropic | API key for Claude models |
GOOGLE_API_KEY |
Google AI | API key for Gemini models |
GROQ_API_KEY |
Groq | API key for Groq models |
DEEPSEEK_API_KEY |
DeepSeek | API key for DeepSeek models |
CEREBRAS_API_KEY |
Cerebras | API key for Cerebras models |
| Variable | Description |
|---|---|
GOOGLE_VERTEX_PROJECT |
GCP project ID |
GOOGLE_VERTEX_LOCATION |
GCP region (e.g., us-central1) |
GOOGLE_APPLICATION_CREDENTIALS |
Path to service account key file |
| Variable | Description |
|---|---|
CORS_ORIGINS |
Comma-separated list of allowed origins |
The Chainguard static image includes a nonroot user (UID 65532):
# Docker
docker run --user 65532:65532 ghcr.io/agentstation/starmap:latest
# docker-compose.yml
services:
starmap:
user: "65532:65532"# docker-compose.yml
services:
starmap:
read_only: true
tmpfs:
- /tmp# docker-compose.yml
services:
starmap:
cap_drop:
- ALL# docker-compose.yml
services:
starmap:
security_opt:
- no-new-privileges:trueDocker Compose:
services:
starmap:
secrets:
- openai_api_key
environment:
- OPENAI_API_KEY_FILE=/run/secrets/openai_api_key
secrets:
openai_api_key:
file: ./secrets/openai.keyKubernetes:
kubectl create secret generic starmap-api-keys \
--from-literal=OPENAI_API_KEY=sk-... \
--from-literal=ANTHROPIC_API_KEY=sk-ant-...apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: starmap-netpol
spec:
podSelector:
matchLabels:
app: starmap
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443 # HTTPS for provider APIs# Check server health
curl http://localhost:8080/api/v1/health
# Expected response
{
"status": "healthy",
"version": "0.0.17",
"timestamp": "2025-01-17T12:00:00Z"
}healthcheck:
test: ["CMD", "/ko-app/starmap", "version"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10slivenessProbe:
exec:
command: ["/ko-app/starmap", "version"]
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10# ServiceMonitor for Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: starmap
spec:
selector:
matchLabels:
app: starmap
endpoints:
- port: http
path: /metrics
interval: 30sDocker Compose:
services:
starmap:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"View logs:
# Docker
docker logs -f starmap-server
# Docker Compose
docker-compose logs -f starmap
# Kubernetes
kubectl logs -f deployment/starmapCheck logs:
docker logs starmap-serverCommon issues:
- Incorrect
HTTP_HOST(use0.0.0.0for containers) - Port already in use
- Missing environment variables
- Insufficient permissions
# Test manually
docker exec starmap-server /ko-app/starmap version
# Check server is listening
docker exec starmap-server netstat -tlnpFrom host to container:
# Verify port mapping
docker port starmap-server
# Test connection
curl -v http://localhost:8080/api/v1/healthFrom container to provider APIs:
# Check DNS resolution
docker exec starmap-server nslookup api.openai.com
# Check connectivity
docker exec starmap-server wget -O- https://api.openai.comEnsure container runs as nonroot user:
user: "65532:65532"# Login to GHCR
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
# Pull with authentication
docker pull ghcr.io/agentstation/starmap:latestInteractive shell (not available in distroless):
The Chainguard static image has no shell. Use debug variants for troubleshooting:
# Use a debug image with shell
docker run -it --entrypoint /bin/sh \
cgr.dev/chainguard/busybox:latest
# Or use kubectl debug for Kubernetes
kubectl debug -it pod/starmap-xxx --image=busybox- Ko Documentation
- Chainguard Images
- Starmap API Reference
- Architecture Documentation
- Contributing Guide
- Issues: GitHub Issues
- Discussions: GitHub Discussions