| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Applies to | Any host with kubectl configured; any Kubernetes cluster |
| Cert prep | CKA · CKAD |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Scope | ServiceAccount creation and assignment, projected token mounts, kubectl create token, automountServiceAccountToken, custom projected audiences, RBAC identity checks with kubectl auth whoami and kubectl auth can-i, and imagePullSecrets on ServiceAccounts. Does not cover full Role YAML, OIDC, human users, token signing keys, or cloud workload identity. |
| Related guides | Kubernetes Secrets |
Every Pod created through the Kubernetes API is assigned a ServiceAccount. Static Pods are an exception because their specifications cannot reference ServiceAccounts or other API objects. That identity is how an in-cluster workload authenticates to the API server; RBAC decides what it may do afterward. This walkthrough follows the lifecycle: inspect the default account, create a custom one, read the projected token mount, request a short-lived token, disable automount, and test authorization separately from identity.
Understand Kubernetes ServiceAccount Identity
Default and custom ServiceAccounts
When you omit serviceAccountName, Kubernetes assigns the namespace default ServiceAccount. ServiceAccounts are namespace-scoped API objects—you cannot reference one from another namespace.
Static Pods are managed directly by the kubelet and cannot reference a ServiceAccount.
ServiceAccount identity versus RBAC permission
A ServiceAccount answers who the Pod is when it calls the API. It does not grant permissions by itself. For how authentication and authorization fit in the request path, see authentication and admission control.
Without a matching RBAC binding, the ServiceAccount normally lacks workload permissions such as listing Pods. It may still have default API-discovery permissions or permissions granted through existing group bindings.
Inspect the Default ServiceAccount Token Mount
Assigned ServiceAccount
I use namespace application and a Pod with no serviceAccountName:
kubectl create namespace applicationSample output:
namespace/application createdWait for the namespace-generated objects required by later examples:
kubectl wait --for=create serviceaccount/default -n application --timeout=60skubectl wait --for=create configmap/kube-root-ca.crt -n application --timeout=60sThe default ServiceAccount is created for each namespace, and the custom projected-volume example later references kube-root-ca.crt.
apiVersion: v1
kind: Pod
metadata:
name: default-identity
namespace: application
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
restartPolicy: NeverApply the manifest and wait until the Pod is Ready:
kubectl apply -f default-identity.yamlkubectl wait --for=condition=Ready pod/default-identity -n application --timeout=60sCheck which ServiceAccount the API stored:
kubectl get pod default-identity -n application -o jsonpath='{.spec.serviceAccountName}{"\n"}'Sample output:
defaultProjected token, CA, and namespace files
The Pod also receives a projected kube-api-access-* volume. List volume names:
kubectl get pod default-identity -n application -o jsonpath='{range .spec.volumes[*]}{.name}{"\n"}{end}'Sample output:
kube-api-access-pv9zqThe default kube-api-access-* volume is a projected volume with three sources:
- ServiceAccount token (bound, short-lived)
- Cluster CA bundle from the
kube-root-ca.crtConfigMap - Namespace via the Downward API
Inside the container, the mount lands at the standard path:
/var/run/secrets/kubernetes.io/serviceaccountList the files:
kubectl exec default-identity -n application -- ls /var/run/secrets/kubernetes.io/serviceaccount/Sample output:
ca.crt
namespace
tokenRead the namespace file the Downward API provides:
kubectl exec default-identity -n application -- cat /var/run/secrets/kubernetes.io/serviceaccount/namespaceSample output:
applicationModern clusters mount short-lived tokens through a projected volume. Kubernetes rotates them; you should not expect a permanent *-token-* Secret to appear on the ServiceAccount object automatically.
Applications that call the API read token, trust ca.crt, and use namespace to build namespaced API paths. This lesson does not decode the JWT; treat it as an opaque credential the API server validates.
Create and Assign a Custom ServiceAccount
Create a dedicated identity:
kubectl create serviceaccount application-reader -n applicationAssign it on the Pod spec:
spec:
serviceAccountName: application-readerFull Pod manifest:
apiVersion: v1
kind: Pod
metadata:
name: app-reader-pod
namespace: application
spec:
serviceAccountName: application-reader
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
restartPolicy: NeverApply the Pod manifest:
kubectl apply -f app-reader-pod.yamlConfirm the assigned identity:
kubectl get pod app-reader-pod -n application -o jsonpath='{.spec.serviceAccountName}{"\n"}'Sample output:
application-readerThe Pod is associated with application-reader. Any process that presents its projected token authenticates as system:serviceaccount:application:application-reader. This article does not create an RBAC binding; the token-based checks below inspect the identity and its current authorization.
Request and Verify a Short-Lived Token
For scripts and debugging outside the Pod, request a token with the API:
kubectl create token application-reader -n applicationSample output (trimmed):
eyJhbGciOiJSUzI1NiIsImtpZCI6IjBJaFVMQjluSDJ1a0xHRXJ5NkttUzBsYou can pass --duration and --audience when you need a specific lifetime or token audience. Prefer kubectl create token over creating a long-lived Secret of type kubernetes.io/service-account-token for normal testing.
Confirm identity with kubectl auth whoami
TOKEN=$(kubectl create token application-reader -n application)When your kubeconfig already contains client certificates or a user identity, kubectl may use that identity instead of --token. Extract the API server endpoint and cluster CA from your current context, then clear the kubeconfig for these checks:
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')CAFILE=/tmp/k8s-ca.crt
kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > "$CAFILE"Confirm which identity the API server sees:
KUBECONFIG=/dev/null kubectl auth whoami \
--token="$TOKEN" \
--server="$APISERVER" \
--certificate-authority="$CAFILE" \
-o jsonpath='{.status.userInfo.username}{"\n"}'Sample output:
system:serviceaccount:application:application-readerkubectl auth whoami supports bearer-token authentication and returns the user attributes seen by the API server.
Test authorization with kubectl auth can-i
Then check authorization using the same token:
KUBECONFIG=/dev/null kubectl auth can-i list pods \
-n application \
--token="$TOKEN" \
--server="$APISERVER" \
--certificate-authority="$CAFILE"Sample output:
nokubectl auth whoami proves that the token authenticates as system:serviceaccount:application:application-reader. The subsequent can-i request checks what that authenticated identity may do. A no result means authentication succeeded but authorization did not grant list pods.
Role and RoleBinding YAML belong in Kubernetes RBAC.
Control ServiceAccount Token Mounting
Workloads that never call the Kubernetes API should not carry API credentials. You can disable automount at the ServiceAccount or Pod level.
Disable automatic mounting
At the ServiceAccount level:
apiVersion: v1
kind: ServiceAccount
metadata:
name: no-mount-sa
namespace: application
automountServiceAccountToken: falseAt the Pod level:
spec:
automountServiceAccountToken: falsePod-level versus ServiceAccount-level precedence
The Pod-level field takes precedence when both the ServiceAccount and Pod define automountServiceAccountToken.
Save both resources in no-mount-pod.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: no-mount-sa
namespace: application
automountServiceAccountToken: false
---
apiVersion: v1
kind: Pod
metadata:
name: no-mount-pod
namespace: application
spec:
serviceAccountName: no-mount-sa
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
restartPolicy: NeverApply the manifest:
kubectl apply -f no-mount-pod.yamlSample output:
serviceaccount/no-mount-sa created
pod/no-mount-pod createdVerify both the identity and absence of the projected volume:
kubectl get pod no-mount-pod \
-n application \
-o jsonpath='serviceAccount={.spec.serviceAccountName}{"\n"}{range .spec.volumes[*]}{.name}{"\n"}{end}'Sample output:
serviceAccount=no-mount-saAn empty volume list means no kube-api-access-* volume was injected. The Pod still runs as no-mount-sa; it simply has no in-cluster API credential mounted.
Mount a custom-audience token
When you need a token at a custom path or for a specific audience, disable default automount and add your own projected volume:
apiVersion: v1
kind: Pod
metadata:
name: custom-audience-pod
namespace: application
spec:
serviceAccountName: application-reader
automountServiceAccountToken: false
volumes:
- name: api-token
projected:
sources:
- serviceAccountToken:
path: token
audience: my-app
expirationSeconds: 3600
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
- downwardAPI:
items:
- path: namespace
fieldRef:
fieldPath: metadata.namespace
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: api-token
mountPath: /var/run/custom-token
readOnly: true
restartPolicy: NeverA token with audience my-app is intended for a recipient that identifies itself as my-app. It is not automatically valid for calls to the Kubernetes API server unless that API server accepts the same audience. The default audience, when omitted, is the API server.
This token is intended for an external service that validates the my-app audience. To call the Kubernetes API, omit audience or use an audience configured in the API server's accepted audience list.
Apply the Pod and inspect the custom mount:
kubectl apply -f custom-audience-pod.yamlkubectl wait pod/custom-audience-pod -n application --for=condition=Ready --timeout=120skubectl exec custom-audience-pod -n application -- ls -1 /var/run/custom-tokenSample output:
ca.crt
namespace
tokenThe projected token is rotated by the kubelet as it approaches expiration. expirationSeconds is a requested lifetime, defaults to one hour, and must be at least 600 seconds.
Use ServiceAccounts with RBAC and imagePullSecrets
Identity and permission are separate layers:
ServiceAccount → RoleBinding/ClusterRoleBinding → Role/ClusterRoleUse the token-based kubectl auth whoami and kubectl auth can-i workflow from earlier to confirm identity and authorization before attaching Roles.
A ServiceAccount can also reference registry pull Secrets:
imagePullSecrets:
- name: regcredPods that use this ServiceAccount inherit those references when their own spec omits imagePullSecrets. The Secret must live in the same namespace as the Pod.
Creating the docker-registry Secret and troubleshooting pull failures is covered in private registry image pulls.
Choose an Account and Troubleshoot Common Failures
| Requirement | Choice |
|---|---|
| Pod does not call Kubernetes API | Dedicated SA with automount disabled, or automountServiceAccountToken: false on the Pod |
| Workload requires specific API permissions | Dedicated ServiceAccount with focused RBAC bindings |
| Quick lab with no API access | Default SA may work, but disable automount in production |
| Registry credentials shared by many Pods | Dedicated SA with imagePullSecrets |
Using the namespace default ServiceAccount everywhere is convenient in labs but couples unrelated workloads to the same identity and token mount behaviour.
| Symptom | Likely cause | Fix |
|---|---|---|
Pod create returns serviceaccount "x" not found |
serviceAccountName is missing from the Pod namespace or misspelled |
Create the ServiceAccount in that namespace or correct the name |
| Deployment has desired replicas but no Pod object | ReplicaSet Pod admission failed, possibly because the ServiceAccount is missing | Run kubectl describe replicaset <name> -n <namespace> and read FailedCreate |
| No token file in the container | automountServiceAccountToken: false on Pod or ServiceAccount |
Re-enable automount or add a custom projected volume when a token is required |
API returns 403 Forbidden with a valid token |
RBAC does not grant the verb; identity succeeded | Attach a RoleBinding or ClusterRoleBinding with the required permissions |
Expected *-token-* Secret on ServiceAccount |
Legacy pattern; modern clusters use bound projected tokens | Use kubectl create token or the projected mount instead |
| Expected ServiceAccount pull Secret is absent from the admitted Pod | The Pod already defines one or more imagePullSecrets, so the ServiceAccount list was not copied |
Remove Pod-level imagePullSecrets when you want the ServiceAccount list copied |
FailedToRetrieveImagePullSecret |
Secret is missing, misspelled, or not in the Pod namespace | Create the Secret in the Pod namespace and match the name exactly |
API calls begin returning 401 Unauthorized after ServiceAccount deletion |
The projected token is bound to the deleted ServiceAccount UID and is no longer valid | Restore the intended ServiceAccount and recreate or verify credential refresh for affected Pods |
Verify which pull Secrets the admitted Pod received:
kubectl get pod <pod-name> -n application -o jsonpath='{.spec.imagePullSecrets}{"\n"}'What's Next
- Deploy Applications on Kubernetes with Helm
- Kubernetes Bases, Overlays and Patches
- Kubernetes Pods and Pod Lifecycle
References
- Configure service accounts for pods — Kubernetes documentation
- Manage service accounts — administrator guide
- Projected volumes — token, CA, and Downward API sources
Summary
A ServiceAccount is the API identity for a Pod. Omit serviceAccountName and you get the namespace default account; set it to a custom name and any container that presents the mounted credential authenticates as system:serviceaccount:namespace:name. Assigning a ServiceAccount associates the identity with the Pod; authentication occurs when a process actually presents the token. By default Kubernetes mounts a projected kube-api-access-* volume at /var/run/secrets/kubernetes.io/serviceaccount with a short-lived token, CA bundle, and namespace file.
Use kubectl create token when you need credentials outside the Pod. Confirm identity with kubectl auth whoami and test authorization with kubectl auth can-i, passing --token with KUBECONFIG=/dev/null so your workstation kubeconfig identity does not override the bearer token. Set automountServiceAccountToken: false on workloads that never call the API, or supply a custom projected volume when you need a specific audience or mount path. A token proves identity only—RBAC decides whether the verb is allowed.
For the request path that runs before RBAC evaluates the caller, see the authentication lesson. For Role and RoleBinding manifests, continue with the RBAC guide.

