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.
kubectlandhelmconfigured 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:
- Horizontal Pod Autoscaler (HPA) — scales based on CPU/memory metrics.
- Cluster Autoscaler — adds nodes when pods are pending.
- Parallel Jobs — use
completionsandparallelismto 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:
statevectorfor exact amplitudes,matrix_product_statefor low-entanglement circuits with many qubits,stabilizerfor Clifford-only circuits (scales to thousands of qubits). - GPU acceleration: Install
qiskit-aer-gpuand requestnvidia.com/gpu: 1in 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
nodeSelectoror taints/tolerations.
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
accelerator: nvidia-a100
Comparison: Qiskit Aer vs. Other Simulators
| Feature | Qiskit Aer | Cirq Simulator | ProjectQ | QuEST |
|---|---|---|---|---|
| Max qubits (statevector, CPU) | ~30 | ~28 | ~30 | ~32 |
| GPU support | ✅ cuStateVec | Limited | ❌ | ✅ |
| Noise modeling | Extensive | Basic | Basic | Limited |
| Ecosystem | Qiskit | Cirq | Native | Standalone |
| Kubernetes-friendly | ✅ | ✅ | ✅ | ✅ |
| Best for | Qiskit workflows, research | Google stack | Educational | HPC 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.