# kubectl Cheat Sheet — Networking & Config

> **Tool:** kubectl
> **Category:** Containers & Orchestration
> **Verified against:** kubectl v1.34.0 (client), flags verified via `kubectl <cmd> --help` run locally, 2026-08-29
> **Official docs:** https://kubernetes.io/docs/reference/kubectl/

Services, ingress, configmaps, secrets, RBAC, and switching between clusters/namespaces via kubeconfig
contexts — the surface area that's less "what's the state of my workload" and more "how does traffic reach it,
and who's allowed to touch it."

## Exposing a deployment as a service

```bash
kubectl expose deployment my-deployment --port=80 --target-port=8080
kubectl expose pod my-pod --port=443 --name=my-frontend
kubectl expose deployment my-deployment --port=80 --type=LoadBalancer   # provision a cloud load balancer, if the cluster supports it
kubectl get services
kubectl get svc my-service -o wide
```

`--port` is what the Service listens on; `--target-port` is the container port traffic gets forwarded to —
they're allowed to differ (e.g. exposing 443 externally while the container listens on 8443).

| Service type | Reachable from |
|---|---|
| `ClusterIP` (default) | Only inside the cluster |
| `NodePort` | Any cluster node's IP, on a fixed high port (30000-32767 by default) |
| `LoadBalancer` | The internet, via a cloud provider's provisioned load balancer (needs cloud-controller-manager support) |
| `ExternalName` | An external DNS name — the Service becomes a CNAME, no proxying involved at all |

## Ingress

```bash
kubectl create ingress simple --rule="app.example.com/*=my-service:80"
kubectl create ingress simple --class=nginx --rule="app.example.com/*=my-service:80,tls=my-tls-secret"
kubectl get ingress
kubectl describe ingress simple
```

`--class` selects which ingress controller handles the resource (e.g. `nginx`, `alb`) — a cluster with no
matching `IngressClass` installed will accept the resource but never actually route traffic for it, which is a
common "why isn't this working" trap: the resource looks fine (`kubectl get ingress` shows it), but nothing is
actually listening for it.

## ConfigMaps

```bash
kubectl create configmap my-config --from-literal=LOG_LEVEL=info --from-literal=ENV=production
kubectl create configmap my-config --from-file=path/to/config.yaml
kubectl create configmap my-config --from-env-file=.env               # bulk-load an entire .env-style file
kubectl get configmap my-config -o yaml
```

## Secrets 🔒

```bash
kubectl create secret generic my-secret --from-literal=DB_PASSWORD=hunter2
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa
kubectl create secret docker-registry my-registry-secret --docker-server=registry.example.com --docker-username=me --docker-password=pw
kubectl get secret my-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d   # decode a value for inspection
```

> [!WARNING]
> **Secret values in `get -o yaml`/`json` are base64-encoded, not encrypted** — base64 is an encoding, not
> security. Anyone with `get secrets` RBAC access can decode it in one command. Rely on RBAC (and, for
> anything sensitive, an external secrets manager or encryption-at-rest with a real KMS-backed provider) for
> actual protection, not the encoding itself.

## Kubeconfig contexts — switching clusters and namespaces

```bash
kubectl config get-contexts                     # list all contexts
kubectl config current-context                  # which one is active
kubectl config use-context my-cluster            # switch active context
kubectl config set-context --current --namespace=my-namespace   # default namespace for the current context
kubectl config view --minify                       # show only the active context's config, not every cluster/user/context in the file
kubectl config delete-context old-cluster
```

```mermaid
flowchart LR
    KC["~/.kube/config"] --> C1["context: dev"]
    KC --> C2["context: staging"]
    KC --> C3["context: prod"]
    C1 --> Cluster1["cluster: dev-cluster<br/>user: dev-creds"]
    C2 --> Cluster2["cluster: staging-cluster<br/>user: staging-creds"]
    C3 --> Cluster3["cluster: prod-cluster<br/>user: prod-creds"]

    classDef crit fill:#fbe8e6,stroke:#b3261e,color:#10161c
    classDef info fill:#e5f0fa,stroke:#1d6fb8,color:#10161c
    class C3,Cluster3 crit
    class C1,C2,Cluster1,Cluster2 info
```

> [!CAUTION]
> **Switching context switches which *cluster and credentials* `kubectl` talks to — every command after that
> is silently pointed at a different cluster.** A common, genuinely damaging incident-response mistake is
> running a `delete` against the wrong cluster because the active context wasn't checked first, especially
> when `dev`/`staging`/`prod` contexts are named similarly. `kubectl config current-context` before anything
> destructive is cheap insurance — many teams also set a colored/labeled shell prompt (via `kube-ps1` or
> similar) specifically so the active context is always visible, not just checkable on demand.

## Port-forwarding to a pod or service

```bash
kubectl port-forward pod/my-pod 8080:80
kubectl port-forward deployment/my-deployment 8080:80
kubectl port-forward service/my-service 8443:https   # target a service's named port
kubectl port-forward pod/my-pod 8080:80 --address=0.0.0.0   # bind on all interfaces, not just localhost — useful from a remote box, riskier on a shared one
```

## RBAC — roles, bindings, and service accounts

```bash
kubectl create serviceaccount my-app-sa
kubectl get serviceaccounts

kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
kubectl create rolebinding my-app-binding --role=pod-reader --serviceaccount=my-namespace:my-app-sa

kubectl create clusterrolebinding my-app-cluster-binding --clusterrole=view --user=jane@example.com

kubectl get roles,rolebindings                    # namespaced RBAC objects, current namespace
kubectl get clusterroles,clusterrolebindings        # cluster-scoped RBAC objects
```

A `Role`/`RoleBinding` pair grants permissions within one namespace; `ClusterRole`/`ClusterRoleBinding` grants
cluster-wide (or, if bound via a namespaced `RoleBinding`, a `ClusterRole`'s rules scoped to just that
namespace — useful for reusing a built-in role like `view`/`edit`/`admin` without redefining it per
namespace).

```bash
kubectl auth can-i create pods --namespace=my-namespace                       # am I allowed to?
kubectl auth can-i list pods --as=system:serviceaccount:my-namespace:my-app-sa   # check as a specific service account
kubectl auth can-i '*' '*' --as=system:serviceaccount:kube-system:my-operator    # a quick "is this effectively cluster-admin" sanity check
```

> [!TIP]
> **`auth can-i` is the fastest way to debug a `Forbidden` error** — it evaluates the same RBAC rules the API
> server would, without you having to trace through Role/RoleBinding YAML by hand across every namespace a
> permission might be granted in.

## Real-world scenario: least-privilege service account for a CI deployer

A CI pipeline needs to deploy into one namespace and nothing else — over-scoping this to `cluster-admin` (a
common shortcut) is a real blast-radius risk if the CI token ever leaks:

```bash
kubectl create serviceaccount ci-deployer -n staging
kubectl create role deployer --verb=get,list,watch,create,update,patch,delete \
  --resource=deployments,services,configmaps,secrets -n staging
kubectl create rolebinding ci-deployer-binding --role=deployer \
  --serviceaccount=staging:ci-deployer -n staging

# Verify the scope is actually what was intended before wiring it into CI:
kubectl auth can-i delete deployments --as=system:serviceaccount:staging:ci-deployer -n staging   # yes
kubectl auth can-i delete deployments --as=system:serviceaccount:staging:ci-deployer -n production   # should be no
```

Verifying with `auth can-i` in both the intended namespace and an unintended one is the step that actually
confirms the RBAC scoping worked, rather than trusting the `create role`/`create rolebinding` commands
succeeded without error — a typo in `--serviceaccount=<namespace>:<name>` (wrong namespace) is an easy mistake
that still exits 0.

## Common pitfalls

- **Not checking `current-context` before a destructive command** — see the CAUTION above.
- **Assuming a Secret is "secure" because it's not plaintext** — see the WARNING above.
- **An Ingress with no matching IngressClass installed** — the resource is accepted but silently does nothing.
- **Scoping a CI service account too broadly** "to save time" — see the real-world scenario above for the
  actual verification step that catches this before it ships.

## When to reach for something else

For managing RBAC/network policy at scale across many namespaces, hand-running `create role`/`create
rolebinding` per namespace doesn't scale — most real platforms template these via Helm/Kustomize or a
policy-as-code tool (see this site's Security & Compliance section for OPA/Conftest, which can enforce RBAC
conventions cluster-wide rather than relying on each team getting the imperative commands right by hand).
