djangoeurope Compared with Kubernetes
Kubernetes runs thousands of containers across many machines, and it's brilliant at that. But for a Django project, it's a lot of machinery. You wanted a website. You got a distributed system.
Who actually needs Kubernetes?
Kubernetes was built for platforms the size of Google's. It shines when many teams ship many services, when traffic has to scale across dozens of machines, and when there's a team whose job is to keep the cluster healthy. If that's you, it's a great tool.
Most Django projects are one application with a database, some background tasks and a few scheduled jobs. They don't need a cluster. They need a server that's well looked after.
Kubernetes pays off when you have
- Many services, run by several teams
- Traffic that scales across dozens of machines
- A platform team that keeps the cluster running
None of these? Then the rest of this page is for you.
With managed Kubernetes from AWS, Google or Azure, the provider runs the control plane. Everything your application needs on top is still yours to build and maintain.
djangoeurope
9 clicks
and a short form each, for all 8 tasks below. We keep everything up to date.
Managed Kubernetes
91+ lines
of YAML and commands for the same tasks, and that's before monitoring, autoscaling and the next upgrade.
djangoeurope
Managed Kubernetes
Getting started
djangoeurope
Nothing to set up. Web, database and mail servers are running, and the one-click installer sets up your Django project.
Ready to go
Managed Kubernetes
Create a cluster and a node pool, connect kubectl, and install a Gateway API implementation and cert-manager before anything can serve a website.
gcloud container clusters create myshop \ --region europe-west3 --num-nodes 1 gcloud container clusters get-credentials myshop \ --region europe-west3 helm install eg oci://docker.io/envoyproxy/gateway-helm \ -n envoy-gateway-system --create-namespace helm install cert-manager jetstack/cert-manager \ -n cert-manager --create-namespace --set crds.enabled=true8 lines to type
Package your app
djangoeurope
Nothing to package. Your project runs straight from your home directory.
Ready to go
Managed Kubernetes
Write a Dockerfile, build an image for every release, and push it to a container registry.
FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["gunicorn", "myshop.wsgi", "-b", "0.0.0.0:8000"] docker build -t registry.example.com/myshop:1.0.3 . docker push registry.example.com/myshop:1.0.38 lines to type
Deploy the Django app
djangoeurope
WebsitesAdd
Choose the domain, the SSH user and the settings you want.
2 clicks + a form
Managed Kubernetes
Describe the app as a Deployment, expose it with a Service, and route traffic to it through the Gateway API.
apiVersion: apps/v1 kind: Deployment metadata: name: myshop spec: replicas: 2 selector: matchLabels: {app: myshop} template: metadata: labels: {app: myshop} spec: containers: - name: web image: registry.example.com/myshop:1.0.3 ports: [{containerPort: 8000}] envFrom: [{secretRef: {name: myshop}}] --- kind: Service ... --- kind: HTTPRoute ...21+ lines to type
Settings and secrets
djangoeurope
Keep your settings in your project and your environment, as usual with Django.
Ready to go
Managed Kubernetes
Move settings into ConfigMaps and passwords into Secrets, and make sure every Pod gets the right ones.
apiVersion: v1 kind: Secret metadata: name: myshop type: Opaque stringData: DJANGO_SECRET_KEY: "..." DATABASE_URL: "postgres://myshop:...@postgres:5432/myshop"8 lines to type
Create a database
djangoeurope
DatabasesAdd database
Enter the database name and choose MySQL or PostgreSQL. It is backed up automatically every day.
2 clicks + a form
Managed Kubernetes
Run PostgreSQL as a StatefulSet with persistent volumes and take care of backups and failover yourself, or rent a managed database on top.
apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: postgres replicas: 1 template: spec: containers: - name: postgres image: postgres:17 volumeClaimTemplates: - metadata: {name: data} spec: resources: {requests: {storage: 20Gi}} ...16+ lines to type
Migrations and cron jobs
djangoeurope
Run manage.py migrate over SSH, and add cron jobs with crontab, like on any server.
Ready to go
Managed Kubernetes
Run migrations as a Job before each release, and scheduled tasks as CronJobs, each with YAML of its own.
apiVersion: batch/v1 kind: Job metadata: name: myshop-migrate-1-0-3 spec: template: spec: restartPolicy: Never containers: - name: migrate image: registry.example.com/myshop:1.0.3 command: ["python", "manage.py", "migrate"] --- kind: CronJob ...14+ lines to type
Get an SSL certificate
djangoeurope
SSLCreate SSL certificate
It is renewed and put into use automatically.
2 clicks + a form
Managed Kubernetes
Configure cert-manager with an issuer, request certificates, and wire them into your gateway.
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: {name: letsencrypt} solvers: - http01: gatewayHTTPRoute: parentRefs: [{name: web}]12 lines to type
Set up e-mail
djangoeurope
E-mailsE-mail accountsAdd
Mailboxes and forwardings, with SPF, DKIM and DMARC set up automatically.
3 clicks + a form
Managed Kubernetes
Kubernetes doesn't do e-mail. Rent a separate e-mail service and connect it to your app and your DNS.
kubectl create secret generic smtp \ --from-literal=EMAIL_HOST=smtp.example.net \ --from-literal=EMAIL_HOST_PASSWORD=... # DNS: SPF, DKIM and DMARC records for the mail service ...4+ lines to type
Running your own cluster means you're the cloud provider too: control plane, network, storage and upgrades included.
djangoeurope
9 clicks
and a short form each, for all 11 tasks below. We keep everything up to date.
Your own cluster
117+ lines
of YAML and commands for the same tasks, plus the cluster itself, which someone has to keep alive.
djangoeurope
Your own cluster
Build the cluster
djangoeurope
Not your problem. We run the servers.
Ready to go
Your own cluster
Set up at least three control-plane nodes so that etcd keeps its quorum when one fails, join the worker nodes, and install a network plugin.
kubeadm init --control-plane-endpoint k8s.example.com:6443 \ --upload-certs --pod-network-cidr 10.244.0.0/16 # on the second and third control-plane node: kubeadm join k8s.example.com:6443 --control-plane \ --token ... --discovery-token-ca-cert-hash sha256:... \ --certificate-key ... # on every worker node: kubeadm join k8s.example.com:6443 --token ... \ --discovery-token-ca-cert-hash sha256:... helm install cilium cilium/cilium -n kube-system10 lines to type
Storage and load balancing
djangoeurope
Not your problem. Storage and networking are part of our servers.
Ready to go
Your own cluster
Without a cloud provider, there are no volumes and no load balancers. Install a storage system like Longhorn or Ceph, and MetalLB to hand out IP addresses.
helm install longhorn longhorn/longhorn \ -n longhorn-system --create-namespace helm install metallb metallb/metallb \ -n metallb-system --create-namespace --- apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: public namespace: metallb-system spec: addresses: ["203.0.113.10-203.0.113.20"]12 lines to type
Keep it alive
djangoeurope
Not your problem. We patch and upgrade the servers for you.
Ready to go
Your own cluster
Back up etcd, watch the cluster, renew its internal certificates, and upgrade Kubernetes several times a year, one minor version at a time, node by node.
etcdctl snapshot save /backup/etcd-$(date +%F).db kubeadm certs check-expiration kubeadm upgrade plan kubeadm upgrade apply v1.x.y kubectl drain node-1 --ignore-daemonsets apt-get install kubelet=1.x.y-* kubectl=1.x.y-* kubectl uncordon node-1 # repeat for every node, every few months ...8+ lines to type
Getting started
djangoeurope
Nothing to set up. Web, database and mail servers are running, and the one-click installer sets up your Django project.
Ready to go
Your own cluster
Install a Gateway API implementation and cert-manager before anything can serve a website.
helm install eg oci://docker.io/envoyproxy/gateway-helm \ -n envoy-gateway-system --create-namespace helm install cert-manager jetstack/cert-manager \ -n cert-manager --create-namespace --set crds.enabled=true4 lines to type
Package your app
djangoeurope
Nothing to package. Your project runs straight from your home directory.
Ready to go
Your own cluster
Write a Dockerfile, build an image for every release, and push it to a container registry.
FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["gunicorn", "myshop.wsgi", "-b", "0.0.0.0:8000"] docker build -t registry.example.com/myshop:1.0.3 . docker push registry.example.com/myshop:1.0.38 lines to type
Deploy the Django app
djangoeurope
WebsitesAdd
Choose the domain, the SSH user and the settings you want.
2 clicks + a form
Your own cluster
Describe the app as a Deployment, expose it with a Service, and route traffic to it through the Gateway API.
apiVersion: apps/v1 kind: Deployment metadata: name: myshop spec: replicas: 2 selector: matchLabels: {app: myshop} template: metadata: labels: {app: myshop} spec: containers: - name: web image: registry.example.com/myshop:1.0.3 ports: [{containerPort: 8000}] envFrom: [{secretRef: {name: myshop}}] --- kind: Service ... --- kind: HTTPRoute ...21+ lines to type
Settings and secrets
djangoeurope
Keep your settings in your project and your environment, as usual with Django.
Ready to go
Your own cluster
Move settings into ConfigMaps and passwords into Secrets, and make sure every Pod gets the right ones.
apiVersion: v1 kind: Secret metadata: name: myshop type: Opaque stringData: DJANGO_SECRET_KEY: "..." DATABASE_URL: "postgres://myshop:...@postgres:5432/myshop"8 lines to type
Create a database
djangoeurope
DatabasesAdd database
Enter the database name and choose MySQL or PostgreSQL. It is backed up automatically every day.
2 clicks + a form
Your own cluster
Run PostgreSQL as a StatefulSet with persistent volumes and take care of backups and failover yourself, or rent a managed database on top.
apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: postgres replicas: 1 template: spec: containers: - name: postgres image: postgres:17 volumeClaimTemplates: - metadata: {name: data} spec: resources: {requests: {storage: 20Gi}} ...16+ lines to type
Migrations and cron jobs
djangoeurope
Run manage.py migrate over SSH, and add cron jobs with crontab, like on any server.
Ready to go
Your own cluster
Run migrations as a Job before each release, and scheduled tasks as CronJobs, each with YAML of its own.
apiVersion: batch/v1 kind: Job metadata: name: myshop-migrate-1-0-3 spec: template: spec: restartPolicy: Never containers: - name: migrate image: registry.example.com/myshop:1.0.3 command: ["python", "manage.py", "migrate"] --- kind: CronJob ...14+ lines to type
Get an SSL certificate
djangoeurope
SSLCreate SSL certificate
It is renewed and put into use automatically.
2 clicks + a form
Your own cluster
Configure cert-manager with an issuer, request certificates, and wire them into your gateway.
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: {name: letsencrypt} solvers: - http01: gatewayHTTPRoute: parentRefs: [{name: web}]12 lines to type
Set up e-mail
djangoeurope
E-mailsE-mail accountsAdd
Mailboxes and forwardings, with SPF, DKIM and DMARC set up automatically.
3 clicks + a form
Your own cluster
Kubernetes doesn't do e-mail. Rent a separate e-mail service and connect it to your app and your DNS.
kubectl create secret generic smtp \ --from-literal=EMAIL_HOST=smtp.example.net \ --from-literal=EMAIL_HOST_PASSWORD=... # DNS: SPF, DKIM and DMARC records for the mail service ...4+ lines to type
The costs that don't show in the YAML
- The control plane costs money before anything runs. AWS charges USD 0.10 per hour for each EKS cluster, about USD 73 a month, and Google and Azure charge similar fees for clusters with an uptime guarantee. Servers, load balancers and storage come on top.
- A reliable cluster needs several machines. Its database, etcd, needs three members to survive the failure of one, and your application wants at least two copies on separate nodes.
- Kubernetes never stands still. It ships about three minor releases a year, and each is supported for about 14 months. Fall behind, and you upgrade under pressure, or pay extra: AWS charges six times as much for clusters on an outdated version.
- Someone has to know all of this. Companies that run Kubernetes seriously have a platform team for it, with people on call. That's the biggest cost of all.
With djangoeurope, you get a server that's looked after, with full shell access, for a few euros a month. We do the patching, the upgrades and the backups.
Rough estimates, per month
≈ €70
Control plane, before a single app runs
AWS
≈ €350
Five worker nodes
AWS
≈ €40
NAT gateway, so pods can reach the internet
AWS
≈ €20
A load balancer for your gateway
AWS
≈ €50
A managed PostgreSQL database
AWS
≈ €100
Monitoring and logs for five nodes
Monitoring service
≈ €21,000
A platform team of three engineers
Typical salaries in Germany
Adds up to about €21,600 a month
djangoeurope: from €5 a month
AWSGoogle CloudMicrosoft Azure