Skip to content

Dev pod

A dev pod is an ephemeral interactive environment — spin it up with kubectl apply, connect via port-forward, do your work, and tear it down when you are done. No GitOps ceremony, no Ingress, no Service. This is the explicit exception to the “commit everything to your repo” rule: dev pods are throwaway by design, and managing them through ArgoCD is ceremonial overhead.

Before using this recipe, confirm:

  • Your tenant is provisioned and you have completed Your first deployment end-to-end.
  • You have kubectl access to your tenant namespaces.
  • You know your tenant name (the <your-tenant> prefix used in namespace names).

Use this recipe when you need:

  • A short-lived interactive environment (JupyterLab, RStudio, a debug shell).
  • Quick prototyping or data exploration without committing manifests to your repo.
  • A sandbox that you will delete within hours or days — not a persistent service.

If your workload runs continuously and serves traffic, see Long-running service. If your workload runs to completion and exits, see Jobs & CronJobs.

Save this manifest locally (not in your workload repo). Replace <your-tenant> with your real tenant name.

dev-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: jupyter
namespace: <your-tenant>-dev
labels:
app: jupyter
spec:
priorityClassName: uber-user-significant
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: notebook
image: quay.io/jupyter/minimal-notebook:2024-08-19
ports:
- containerPort: 8888
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2
memory: 4Gi
volumeMounts:
- name: home
mountPath: /home/jovyan
- name: tmp
mountPath: /tmp
volumes:
- name: home
emptyDir: {}
- name: tmp
emptyDir: {}

Before applying, validate the manifest locally:

Terminal window
kubectl apply --dry-run=client -f dev-pod.yaml

This should render without errors. It catches YAML syntax issues and missing required fields before the cluster tries to create the pod.

Create the namespace first if it does not already exist — Kestrel does not pre-create any namespaces, and the name must carry your tenant prefix (see Known limitations):

Terminal window
kubectl create ns <your-tenant>-dev

Then apply the pod directly — no commit, no ArgoCD:

Terminal window
kubectl apply -f dev-pod.yaml

Wait for the pod to start:

Terminal window
kubectl get pod jupyter -n <your-tenant>-dev --watch

Once the status shows Running, read the Jupyter access token from the logs:

Terminal window
kubectl logs jupyter -n <your-tenant>-dev

Look for a line like:

http://127.0.0.1:8888/lab?token=abc123...

Copy the token value. Then start port-forwarding:

Terminal window
kubectl port-forward -n <your-tenant>-dev pod/jupyter 8888:8888

Open your browser to http://localhost:8888/?token=<token> (paste the token from the logs). The JupyterLab interface should load.

When you are done, delete the pod:

Terminal window
kubectl delete pod jupyter -n <your-tenant>-dev

Everything in the emptyDir volumes is deleted with the pod. If you created notebooks or data you want to keep, download them from JupyterLab before deleting.

  • Pod rejected by Kyverno with PSS admission error. You modified the securityContext block. Revert to the verbatim YAML above. See Known limitations for the full PSS rule.
  • Namespace rejected with forceTenantPrefix. The namespace name does not start with your tenant name. All namespaces must be <your-tenant>-<suffix>. See Known limitations.
  • kubectl apply fails with exceeded quota. ResourceQuota rejects the pod at creation time — it never reaches Pending. Delete other workloads in your tenant to free capacity, or request more quota via RCS ticket.
  • Pod stuck in Pending. The pod was admitted but cannot be scheduled — usually cluster capacity or scheduling constraints. Check kubectl describe pod -n <your-tenant>-dev jupyter — the Events section shows the reason.
  • Jupyter shows Permission denied errors. The emptyDir mount at /home/jovyan may be missing. Verify the volumeMounts and volumes sections match the YAML above exactly.
  • Port-forward disconnects or hangs. This is a known kubectl behavior — it drops idle connections. Re-run the kubectl port-forward command. Your Jupyter session state persists as long as the pod is still running.
  • Want this notebook to persist long-term? Promote it to a Long-running service — commit a Deployment manifest to your workload repo with the same image and resource requests, add a Service and Ingress for stable access, and let ArgoCD manage the lifecycle.