ByteGuideHub
Quantum Computing • • 9 min read

Building a Self-Hosted Quantum Computing Simulator with Qiskit and Kubernetes

Step-by-step guide to deploy a scalable quantum circuit simulator using Qiskit and Kubernetes. Learn containerization, orchestration, and optimization for research.

Sponsored / Ad Space

Building a Self-Hosted Quantum Computing Simulator with Qiskit and Kubernetes

Quick Summary (TL;DR)

  • Self-hosting Qiskit Aer on Kubernetes gives you unlimited, cost-controlled access to quantum circuit simulation with horizontal scaling.
  • Containerize Qiskit Aer with Docker, deploy it as Kubernetes Jobs or a JupyterHub service, and scale by adding nodes or GPU pods.
  • Use Persistent Volumes for results, Prometheus/Grafana for monitoring, and Argo Workflows for complex pipelines.
  • Small circuits (≤20 qubits) run fine on 4 CPU cores/8GB RAM; 30+ qubits need 32GB+ RAM or GPU acceleration via cuStateVec.

Introduction: Why Self-Host a Quantum Simulator?

Cloud quantum services are convenient, but they come with queue times, per-shot billing, and data leaving your network. For researchers running thousands of circuit variations, or teams handling sensitive algorithms, a self-hosted simulator becomes the pragmatic choice. You control the hardware, the cost curve flattens, and you can iterate without waiting in someone else’s queue.

Qiskit Aer is IBM’s high-performance simulator engine, capable of handling statevector, density matrix, and stabilizer simulations with realistic noise models. Kubernetes provides the orchestration layer to run those simulations at scale—spinning up parallel jobs, allocating GPU resources, and tearing everything down when the work is done. Together, they form a production-grade quantum research platform.

This guide walks through the full deployment: containerization, orchestration, scaling, monitoring, and optimization.

Prerequisites and Architecture Overview

Before you start, ensure you have:

  • A working Kubernetes cluster (v1.24+) — Minikube for local dev, or a managed cluster (EKS, GKE, AKS) for production.
  • Docker installed locally for image builds.
  • kubectl and helm configured against your cluster.
  • Basic familiarity with Python and Qiskit.
  • (Optional) NVIDIA GPU nodes with the NVIDIA device plugin for GPU acceleration.

The architecture separates concerns: a client submits circuits, a Kubernetes Job runs the simulation in a containerized Aer worker, and results land in persistent storage.

flowchart TD
    A[Client / JupyterHub] -->|Submit Job| B[Kubernetes API Server]
    B --> C[Scheduler]
    C --> D[Aer Worker Pod 1]
    C --> E[Aer Worker Pod 2]
    C --> F[Aer Worker Pod N]
    D --> G[(Persistent Volume / S3)]
    E --> G
    F --> G
    D --> H[Prometheus]
    E --> H
    F --> H
    H --> I[Grafana Dashboard]

This topology lets you scale workers independently and keep result storage decoupled from compute.

Step 1: Containerizing Qiskit with Docker

Start with a lean, reproducible image. We’ll use a multi-stage build to keep the final image small.

# Dockerfile
FROM python:3.11-slim AS builder

RUN pip install --no-cache-dir --upgrade pip
RUN pip install --no-cache-dir qiskit==1.2.0 qiskit-aer==0.15.0

FROM python:3.11-slim
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

WORKDIR /app
COPY simulate.py .

RUN useradd -m qiskit && chown -R qiskit /app
USER qiskit

ENTRYPOINT ["python", "simulate.py"]

The simulate.py script reads a circuit definition from an environment variable or mounted file and writes results to /results:

# simulate.py
import os, json
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

def build_ghz(n: int) -> QuantumCircuit:
    qc = QuantumCircuit(n, n)
    qc.h(0)
    for i in range(n - 1):
        qc.cx(i, i + 1)
    qc.measure(range(n), range(n))
    return qc

if __name__ == "__main__":
    n_qubits = int(os.getenv("QUBITS", "20"))
    shots = int(os.getenv("SHOTS", "1024"))

    sim = AerSimulator(method="statevector")
    circuit = transpile(build_ghz(n_qubits), sim)
    result = sim.run(circuit, shots=shots).result()

    output = {"counts": result.get_counts(), "qubits": n_qubits}
    with open("/results/output.json", "w") as f:
        json.dump(output, f)
    print(f"Simulation complete: {n_qubits} qubits, {shots} shots")

Build and push the image:

docker build -t your-registry/qiskit-aer:0.15.0 .
docker push your-registry/qiskit-aer:0.15.0

Step 2: Deploying Qiskit on Kubernetes

With the image ready, define a Kubernetes Job. Jobs are ideal for batch simulations—they run to completion and don’t consume resources when idle.

# qiskit-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: qiskit-sim-ghz20
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: aer
          image: your-registry/qiskit-aer:0.15.0
          env:
            - name: QUBITS
              value: "20"
            - name: SHOTS
              value: "4096"
          resources:
            requests:
              cpu: "2"
              memory: "4Gi"
            limits:
              cpu: "4"
              memory: "8Gi"
          volumeMounts:
            - name: results
              mountPath: /results
      volumes:
        - name: results
          persistentVolumeClaim:
            claimName: qiskit-results-pvc

Create the PVC first:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: qiskit-results-pvc
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 50Gi

Apply both:

kubectl apply -f pvc.yaml
kubectl apply -f qiskit-job.yaml
kubectl logs job/qiskit-sim-ghz20 -f

For interactive work, deploy JupyterHub with the same image as the single-user container. Helm makes this straightforward:

helm repo add jupyterhub https://hub.jupyter.org/helm-chart/
helm install jhub jupyterhub/jupyterhub \
  --set singleuser.image.name=your-registry/qiskit-aer \
  --set singleuser.image.tag=0.15.0

Step 3: Scaling and Managing Simulations

Kubernetes gives you three scaling levers:

  1. Horizontal Pod Autoscaler (HPA) — scales based on CPU/memory metrics.
  2. Cluster Autoscaler — adds nodes when pods are pending.
  3. Parallel Jobs — use completions and parallelism to fan out parameter sweeps.

For a parameter sweep across 50 circuit depths:

spec:
  completions: 50
  parallelism: 10
  completionMode: Indexed
  template:
    spec:
      containers:
        - name: aer
          env:
            - name: DEPTH
              valueFrom:
                fieldRef:
                  fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

For complex DAGs (transpile → simulate → post-process), Argo Workflows is the cleaner abstraction:

sequenceDiagram
    participant User
    participant Argo
    participant K8s
    participant Aer
    User->>Argo: Submit workflow
    Argo->>K8s: Create transpile pod
    K8s->>Aer: Run transpile
    Aer-->>Argo: Transpiled circuit
    Argo->>K8s: Create N simulate pods
    K8s->>Aer: Parallel simulation
    Aer-->>Argo: Results
    Argo->>User: Aggregated output

Step 4: Monitoring and Logging

Production simulations need observability. Deploy the kube-prometheus-stack via Helm:

helm install monitoring prometheus-community/kube-prometheus-stack

Instrument your simulate.py with Prometheus metrics:

from prometheus_client import Counter, Histogram, push_to_gateway

sim_duration = Histogram("qiskit_sim_seconds", "Simulation duration")
shots_total = Counter("qiskit_shots_total", "Total shots executed")

with sim_duration.time():
    result = sim.run(circuit, shots=shots).result()
shots_total.inc(shots)
push_to_gateway("pushgateway.monitoring:9091", job="qiskit", registry=registry)

For logs, use the EFK stack (Elasticsearch, Fluent Bit, Kibana) or Loki with Grafana. Structured JSON logs from Python make filtering by qubit count or job ID trivial.

Step 5: Advanced Configuration and Optimization

A few tuning levers deliver outsized gains:

  • Simulation method: statevector for exact amplitudes, matrix_product_state for low-entanglement circuits with many qubits, stabilizer for Clifford-only circuits (scales to thousands of qubits).
  • GPU acceleration: Install qiskit-aer-gpu and request nvidia.com/gpu: 1 in the pod spec.
  • Memory limits: Statevector memory grows as 2ⁿ × 16 bytes. A 30-qubit state needs ~16GB; 32 qubits need ~64GB.
  • Caching transpilation: Persist transpiled circuits to PVC so repeated runs skip compilation.
  • Node affinity: Pin GPU pods to GPU nodes with nodeSelector or taints/tolerations.
resources:
  limits:
    nvidia.com/gpu: 1
nodeSelector:
  accelerator: nvidia-a100

Comparison: Qiskit Aer vs. Other Simulators

FeatureQiskit AerCirq SimulatorProjectQQuEST
Max qubits (statevector, CPU)~30~28~30~32
GPU support✅ cuStateVecLimited❌✅
Noise modelingExtensiveBasicBasicLimited
EcosystemQiskitCirqNativeStandalone
Kubernetes-friendly✅✅✅✅
Best forQiskit workflows, researchGoogle stackEducationalHPC clusters

Aer wins when you’re already in the Qiskit ecosystem or need detailed noise models. QuEST is faster for pure statevector workloads on HPC. Cirq integrates tightly with Google’s quantum hardware.

Conclusion and Next Steps

Self-hosting a Qiskit simulator on Kubernetes transforms quantum research from a metered cloud service into an elastic, private compute platform. You’ve containerized Aer, deployed it as Jobs and JupyterHub, wired up autoscaling, and added observability. From here, consider:

  • Integrating with Argo Workflows for multi-stage algorithm pipelines.
  • Adding GPU nodes for 30+ qubit simulations.
  • Building a web frontend that submits Jobs via the Kubernetes API.
  • Exploring hybrid workflows that dispatch large circuits to real quantum hardware while simulating the rest.

The stack is yours. Scale it as your circuits grow.


FAQ

What are the hardware requirements for running a Qiskit simulator on Kubernetes? At minimum, a Kubernetes cluster with nodes having 4 CPU cores and 8GB RAM per pod for small circuits (up to 20 qubits). For larger simulations (30+ qubits), you’ll need nodes with 32GB+ RAM and potentially GPU acceleration. Kubernetes allows horizontal scaling by adding more nodes.

Can I use GPU acceleration with Qiskit Aer in Kubernetes? Yes. Qiskit Aer supports GPU acceleration via cuStateVec. Build a Docker image with CUDA support, ensure your Kubernetes nodes have NVIDIA GPUs, and install the NVIDIA device plugin. Then request GPU resources in your pod spec with nvidia.com/gpu: 1.

How do I persist quantum simulation results in Kubernetes? Use Persistent Volumes (PV) and Persistent Volume Claims (PVC) to store results. Alternatively, integrate with cloud storage (S3, GCS) or a database. For ephemeral jobs, output results to stdout and capture logs.

What is the best way to manage multiple quantum simulation jobs? Use Kubernetes Jobs or CronJobs for batch simulations. For interactive workloads, deploy a JupyterHub instance with Qiskit installed. For complex pipelines, use Argo Workflows to orchestrate dependencies.

Is it possible to run a quantum simulator on a single-node Kubernetes cluster like Minikube? Yes—Minikube or Kind work fine for development and testing. For production-scale simulations, use a multi-node cluster to leverage horizontal scaling and resource isolation.

How does Qiskit Aer compare to other simulators like ProjectQ or Cirq? Aer is highly optimized for Qiskit circuits and offers both CPU and GPU support with extensive noise modeling. ProjectQ and Cirq have their own strengths, but Aer integrates seamlessly with the Qiskit ecosystem. The choice depends on your existing toolchain.

How can I secure my Qiskit simulator deployment? Implement network policies to restrict traffic, use RBAC for access control, run containers as non-root users, and regularly update images. For sensitive data, enable encryption at rest and in transit, and consider a service mesh for mTLS.

Sponsored / Ad Space