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.

  1. 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=true

    8 lines to type

  2. 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.3

    8 lines to type

  3. 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

  4. 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

  5. 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

  6. 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

  7. 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

  8. 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.

  1. 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-system

    10 lines to type

  2. 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

  3. 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

  4. 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=true

    4 lines to type

  5. 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.3

    8 lines to type

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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

  11. 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

From public on-demand list prices at AWS in Frankfurt, converted roughly to euros, and typical salaries in Germany, including employer costs. Data transfer and storage come on top. Google Cloud and Microsoft Azure charge similar prices.

Keep it simple

Run your Django project, not a cluster.