Spry with Kubernetes: Deploying Dart Servers as Scalable Containerized Workloads
Spry with Kubernetes: Deploying Dart Microservices at Scale
Deploy your Spry Dart applications on Kubernetes with production‑ready configurations, automatic scaling, rolling updates, and GitOps workflows.
Kubernetes has become the de‑facto standard for container orchestration, providing robust deployment, scaling, and management capabilities for modern applications. With Spry’s lightweight architecture and Docker support, you can run Dart microservices on Kubernetes alongside traditional stacks, leveraging the same infrastructure tooling.
In this tutorial, you’ll learn:
- Why run Dart on Kubernetes? Standardization, scalability, and ecosystem integration
- How to package Spry applications as Docker images for Kubernetes
- Kubernetes manifests for Spry deployments, services, and ingress
- Helm charts for templated, reusable deployments
- Monitoring, logging, and auto‑scaling configurations for Dart services
- GitOps workflows with ArgoCD or Flux for continuous deployment
All examples are based on a real Spry microservice and can be adapted for your own projects.
Why Kubernetes for Spry?
- Standardization – Use the same orchestration platform as the rest of your infrastructure
- Scalability – Horizontal pod autoscaling based on CPU, memory, or custom metrics
- High availability – Replica sets, rolling updates, and self‑healing
- Ecosystem integration – Leverage existing CNCF tools for monitoring, security, and networking
- Portability – Run the same manifests on any cloud or on‑prem Kubernetes cluster
- GitOps readiness – Declarative configuration that can be managed via Git
Prerequisites
- A running Kubernetes cluster (minikube, kind, k3d, or cloud‑managed)
- Docker Desktop (or Docker Engine + kubectl)
- Helm (optional, for package management)
- Basic familiarity with Kubernetes concepts (pods, deployments, services, ingress)
- Spry application ready for containerization
Project Structure
spry‑k8s‑example/
├── Dockerfile.spry # Multi‑stage Dockerfile (dev/prod)
├── k8s/
│ ├── base/ # Kustomize base (optional)
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── ingress.yaml
│ │ └── kustomization.yaml
│ ├── overlays/
│ │ ├── development/
│ │ └── production/
│ └── helm/
│ └── spry‑app/
│ ├── Chart.yaml
│ ├── values.yaml
│ ├── templates/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── ingress.yaml
│ │ ├── configmap.yaml
│ │ └── _helpers.tpl
│ └── charts/
├── lib/
│ └── main.dart # Spry application entry point
├── config/
│ └── kubernetes.env # Environment‑specific settings
├── scripts/
│ └── deploy‑k8s.sh # Deployment automation
└── README.md
1. Docker Image for Kubernetes
Create a production‑optimized Dockerfile.spry:
# ──────────────────────────────────────────────────────────────────────────────
# Builder stage (AOT compilation)
# ──────────────────────────────────────────────────────────────────────────────
FROM dart:stable AS builder
WORKDIR /app
COPY pubspec.yaml pubspec.lock ./
RUN dart pub get --offline
COPY . .
RUN dart compile exe lib/main.dart -o /app/bin/spry‑server
# ──────────────────────────────────────────────────────────────────────────────
# Runtime stage (minimal)
# ──────────────────────────────────────────────────────────────────────────────
FROM debian:bookworm‑slim
# Install runtime dependencies
RUN apt‑get update && apt‑get install ‑y \
ca‑certificates \
tzdata \
&& rm ‑rf /var/lib/apt/lists/*
# Create non‑root user
RUN groupadd ‑r spry && useradd ‑r ‑g spry spry
USER spry
WORKDIR /app
# Copy compiled binary
COPY ‑‑from=builder /app/bin/spry‑server /app/bin/
# Copy runtime assets (static files, templates, etc.)
COPY ‑‑from=builder /app/public /app/public
# Health check (HTTP endpoint /health)
HEALTHCHECK ‑‑interval=30s ‑‑timeout=3s ‑‑start‑period=5s ‑‑retries=3 \
CMD ["/app/bin/spry‑server", "‑‑health‑check"] || exit 1
EXPOSE 8080
ENTRYPOINT ["/app/bin/spry‑server"]
Build and push to a container registry:
docker build -f Dockerfile.spry -t yourregistry/spry‑app:v1.0.0 .
docker push yourregistry/spry‑app:v1.0.0
2. Kubernetes Manifests
Deployment (k8s/base/deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: spry‑app
labels:
app: spry‑app
tier: backend
spec:
replicas: 3
selector:
matchLabels:
app: spry‑app
template:
metadata:
labels:
app: spry‑app
version: v1.0.0
spec:
containers:
- name: spry‑app
image: yourregistry/spry‑app:v1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: spry‑secrets
key: database‑url
- name: REDIS_URL
valueFrom:
configMapKeyRef:
name: spry‑config
key: redis‑url
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 10
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- spry‑app
topologyKey: kubernetes.io/hostname
Service (k8s/base/service.yaml)
apiVersion: v1
kind: Service
metadata:
name: spry‑app
spec:
selector:
app: spry‑app
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
type: ClusterIP
Ingress (k8s/base/ingress.yaml)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: spry‑app
annotations:
nginx.ingress.kubernetes.io/rewrite‑target: /
cert‑manager.io/cluster‑issuer: letsencrypt‑production
spec:
ingressClassName: nginx
tls:
- hosts:
- spry.example.com
secretName: spry‑tls
rules:
- host: spry.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: spry‑app
port:
number: 80
ConfigMap and Secrets
# k8s/base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: spry‑config
data:
redis‑url: "redis://redis‑service:6379"
log‑level: "info"
---
# Secrets should be created separately (kubectl create secret generic)
# kubectl create secret generic spry‑secrets \
# --from‑literal=database‑url=postgres://...
Kustomization (k8s/base/kustomization.yaml)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
- configmap.yaml
commonLabels:
app: spry‑app
managed‑by: kustomize
3. Helm Chart (Optional)
For advanced packaging, create a Helm chart:
helm create k8s/helm/spry‑app
Edit values.yaml:
# k8s/helm/spry‑app/values.yaml
replicaCount: 3
image:
repository: yourregistry/spry‑app
tag: v1.0.0
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: spry.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: spry‑tls
hosts:
- spry.example.com
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 256Mi
cpu: 500m
env:
DATABASE_URL: ""
REDIS_URL: "redis://redis‑service:6379"
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
Deploy with Helm:
helm upgrade --install spry‑app k8s/helm/spry‑app \
--namespace spry‑production \
--values k8s/helm/spry‑app/values‑production.yaml
4. Spry Application with Kubernetes‑Aware Features
Update your Spry application to be Kubernetes‑friendly:
// lib/main.dart
import 'dart:io';
import 'package:spry/spry.dart';
import 'package:spry_health/spry_health.dart';
Future<void> main() async {
final app = Application();
// Add Kubernetes readiness/liveness endpoints
final health = Health();
app.use(health.middleware());
app.get('/health', (request) async {
// Add custom health checks (database, cache, external services)
final checks = <String, String>{};
checks['app'] = 'healthy';
checks['timestamp'] = DateTime.now().toIso8601String();
return Response.json(checks);
});
app.get('/ready', (request) async {
// readiness check (dependencies available)
final ready = await checkDependencies();
if (ready) {
return Response.text('READY');
} else {
return Response.text('NOT READY', status: 503);
}
});
// Graceful shutdown handling for Kubernetes termination signals
ProcessSignal.sigterm.watch().listen((_) async {
print('Received SIGTERM, shutting down gracefully...');
await app.close();
exit(0);
});
// Your application routes
app.get('/', (request) async {
return Response.text('Spry on Kubernetes 🚀');
});
final server = await app.listen(port: 8080);
print('Server running on http://${server.address.host}:${server.port}');
}
5. Deployment Workflow
Local Development with Minikube
# Start minikube
minikube start --cpus=4 --memory=8192
# Enable ingress
minikube addons enable ingress
# Build image directly in minikube's Docker daemon
eval $(minikube docker‑env)
docker build -f Dockerfile.spry -t spry‑app:latest .
# Deploy manifests
kubectl apply -k k8s/base/
# Access via minikube IP
minikube service spry‑app --url
Production Deployment
# Apply base manifests
kubectl apply -k k8s/overlays/production/
# Or deploy with Helm
helm upgrade --install spry‑app k8s/helm/spry‑app \
--namespace production \
--create-namespace \
--values k8s/helm/spry‑app/values-production.yaml
# Monitor rollout
kubectl rollout status deployment/spry‑app -n production
6. Monitoring and Observability
Prometheus Metrics
Add spry_prometheus middleware:
import 'package:spry_prometheus/spry_prometheus.dart';
final prometheus = Prometheus();
app.use(prometheus.middleware());
app.get('/metrics', (request) async {
return Response.text(prometheus.export());
});
Configure Prometheus scrape annotation in deployment:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
Logging
Use structured JSON logging:
import 'package:logger/logger.dart';
final logger = Logger(
printer: JsonPrinter(),
);
app.use((request, next) async {
final start = DateTime.now();
final response = await next(request);
final duration = DateTime.now().difference(start);
logger.i({
'method': request.method,
'path': request.path,
'status': response.status,
'duration_ms': duration.inMilliseconds,
'user_agent': request.headers['user‑agent'],
});
return response;
});
Distributed Tracing
Integrate with Jaeger or OpenTelemetry.
7. GitOps with ArgoCD
Create an ArgoCD Application manifest:
# apps/spry‑app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: spry‑app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/spry‑k8s‑example
targetRevision: main
path: k8s/overlays/production
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
8. Troubleshooting
Common Issues
- Image pull errors – Ensure registry credentials are in
imagePullSecrets - CrashLoopBackOff – Check logs:
kubectl logs deployment/spry‑app ‑‑previous - Readiness probe failures – Verify
/readyendpoint responds correctly - Resource limits – Adjust requests/limits based on Dart VM memory usage
- DNS resolution – Use Kubernetes service names (e.g.,
postgres‑service:5432)
Debugging Commands
# View pods
kubectl get pods -l app=spry‑app
# View logs
kubectl logs -f deployment/spry‑app
# Port forward for local debugging
kubectl port-forward deployment/spry‑app 8080:8080
# Exec into container
kubectl exec -it deployment/spry‑app -- sh
# Describe resource
kubectl describe deployment spry‑app
Conclusion
Kubernetes provides a robust, scalable platform for running Spry Dart applications in production. With proper manifests, health checks, and monitoring, you can deploy Dart microservices alongside your existing stack with confidence.
This tutorial covered the essential patterns—Docker images, Kubernetes manifests, Helm charts, and GitOps workflows—to get your Spry application running on Kubernetes.
Next Steps:
- Implement CI/CD – Automate image builds and deployments on push
- Add service mesh – Explore Istio or Linkerd for advanced traffic management
- Multi‑cluster deployment – Deploy across regions for high availability
- Cost optimization – Use node autoscaling and resource tuning
- Security hardening – Network policies, PodSecurity admission, and secrets management
All code from this tutorial is available in the spry‑k8s‑example GitHub repository.
Happy orchestrating! 🚀
This is a draft tutorial. Final content will include more detailed examples, troubleshooting guides, and best practices for production deployments.