Sometimes you just want to look at your cluster instead of typing another kubectl get pods -o wide. The official Kubernetes Dashboard gives you a web UI for exactly that — browsing workloads, checking logs, viewing resource usage, and even editing objects directly, all without leaving the browser. In this guide, I’ll walk through installing it properly, securing access (which is where most people get it wrong), and integrating it with AWS EKS’s IAM-based authentication model.
What the Kubernetes Dashboard Actually Is
The Kubernetes Dashboard is a general-purpose, web-based UI for Kubernetes clusters. It lets you deploy applications, troubleshoot them, and manage cluster resources. It talks to the Kubernetes API server using a ServiceAccount token (or your own credentials, depending on configuration) — it has no special privileges of its own beyond whatever identity you authenticate it with.
It’s worth being upfront about something: the Dashboard has historically been a security liability when misconfigured, largely because early tutorials (and unfortunately, some attackers’ favorite misconfigurations) exposed it publicly with cluster-admin access. We’ll do this properly.
Installing the Dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
Check it deployed:
kubectl get pods -n kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
dashboard-metrics-scraper-6b6d9c9b7d-abcde 1/1 Running 0 1m
kubernetes-dashboard-7f8d9c6b5d-fghij 1/1 Running 0 1m
For a more configurable install with Helm (recommended for production, since it gives you clean upgrade paths):
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
helm repo update
helm install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard \
--namespace kubernetes-dashboard --create-namespace \
--set metricsScraper.enabled=true
Creating a Restricted ServiceAccount for Dashboard Access
This is the step most tutorials skip or get wrong. Never bind the Dashboard’s access to cluster-admin by default. Instead, create a purpose-specific ServiceAccount with exactly the permissions needed:
apiVersion: v1
kind: ServiceAccount
metadata:
name: dashboard-viewer
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-viewer-role
rules:
- apiGroups: [""]
resources: ["pods", "services", "endpoints", "persistentvolumeclaims", "events", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-viewer-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dashboard-viewer-role
subjects:
- kind: ServiceAccount
name: dashboard-viewer
namespace: kubernetes-dashboard
Notice this is read-only — no create, update, delete, or patch verbs. This is deliberate: most people using a dashboard want visibility, not another way to accidentally kubectl delete production. Grant write access only to specific individuals who genuinely need it, through a separate, more privileged ServiceAccount, scoped as narrowly as possible (namespace-scoped RoleBinding rather than cluster-wide where feasible).
Generate a token to log in:
kubectl create token dashboard-viewer -n kubernetes-dashboard --duration=8h
This produces a short-lived bearer token (8 hours here) rather than the old pattern of long-lived Secret-based ServiceAccount tokens, which were deprecated as a default in favor of the TokenRequest API precisely because indefinitely-valid tokens are a standing security risk.
Accessing the Dashboard Securely
Never expose the Dashboard directly to the public internet. The correct pattern is kubectl proxy or port-forward for ad hoc access, or an internal-only Ingress behind SSO/VPN for team-wide access.
Option 1: kubectl proxy (best for individual, ad hoc access)
kubectl proxy
Then visit:
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
Paste in the token generated above when prompted.
Option 2: Internal Ingress with SSO (best for team access)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kubernetes-dashboard
namespace: kubernetes-dashboard
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internal
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/xxxx
alb.ingress.kubernetes.io/auth-type: oidc
alb.ingress.kubernetes.io/auth-idp-oidc: |
{"issuer":"https://your-idp.com","authorizationEndpoint":"https://your-idp.com/authorize","tokenEndpoint":"https://your-idp.com/token","userInfoEndpoint":"https://your-idp.com/userinfo","secretName":"oidc-secret"}
spec:
rules:
- host: dashboard.internal.company.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubernetes-dashboard
port:
number: 443
This puts an OIDC authentication layer (via the ALB’s native OIDC support) in front of the Dashboard, on an internal (VPC-only) load balancer scheme — meaning it’s not reachable from the public internet at all, and requires SSO login before even reaching the Dashboard’s own token prompt.
Integrating with AWS IAM Identity
Rather than distributing static ServiceAccount tokens to every team member, a more elegant approach on EKS ties Dashboard access to IAM identity via aws eks get-token, so team members authenticate with their existing AWS credentials:
aws eks update-kubeconfig --name production-cluster --region us-east-1
Then use kubectl proxy as above — since your kubeconfig is already using the AWS IAM authenticator, RBAC bindings for your IAM role/user (rather than a shared ServiceAccount) control what the Dashboard shows you, giving proper per-user audit trails instead of everyone sharing one token.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alice-dashboard-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dashboard-viewer-role
subjects:
- kind: User
name: alice@company.com
apiGroup: rbac.authorization.k8s.io
(Mapped via an EKS access entry associating the IAM principal with that Kubernetes username.)
Using the Dashboard
Once logged in, you get:
- Workloads overview — Deployments, Pods, ReplicaSets, StatefulSets, DaemonSets, Jobs, with status at a glance
- Logs viewer — click into any pod to tail logs without a terminal
- Resource usage graphs — CPU/memory over time, if
metrics-serveris installed - Exec into a container — a web-based terminal, if RBAC permits
pods/exec - YAML editor — view and edit any object’s manifest directly (again, gated by RBAC verbs)
Install metrics-server if you haven’t already, since the Dashboard’s usage graphs depend on it:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
Alternative: Headlamp
Worth mentioning — Headlamp is a newer, actively developed alternative to the official Dashboard, with a cleaner UI and a plugin system, backed by the CNCF:
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
helm install headlamp headlamp/headlamp --namespace kube-system
It follows the same RBAC-driven security model, so everything above about scoping access still applies.
Namespace-Scoped Dashboard Access for Teams
In a multi-tenant cluster (see the companion multi-tenancy article), you generally don’t want every team seeing every other team’s workloads in the Dashboard, even read-only. Scope access with a namespace-bound RoleBinding rather than the cluster-wide ClusterRoleBinding shown earlier:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: acme-dashboard-viewer
namespace: tenant-acme
subjects:
- kind: Group
name: acme-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: dashboard-viewer-role
apiGroup: rbac.authorization.k8s.io
Because this is a RoleBinding (not a ClusterRoleBinding) referencing a ClusterRole, the permissions defined in dashboard-viewer-role apply only within the tenant-acme namespace — a useful trick that lets you define one reusable ClusterRole and bind it narrowly, per-tenant, rather than writing a separate Role for every team from scratch. When a member of acme-admins logs into the Dashboard, they’ll only see resources within namespaces they have a binding for; other tenants’ workloads simply won’t appear.
Auditing Dashboard Activity
Since the Dashboard authenticates through the same API server as kubectl, every action taken through it shows up in the Kubernetes audit log like any other API call, which on EKS means it flows into CloudWatch Logs if control plane logging is enabled:
aws eks update-cluster-config \
--name production-cluster \
--logging '{"clusterLogging":[{"types":["audit"],"enabled":true}]}'
aws logs filter-log-events \
--log-group-name /aws/eks/production-cluster/cluster \
--filter-pattern '{ $.user.username = "alice@company.com" }'
This is exactly why tying Dashboard access to individual IAM/OIDC identities (rather than a single shared ServiceAccount token) matters in practice — without it, every audit log entry for Dashboard activity shows the same generic ServiceAccount name, and you lose the ability to answer “who actually did this” during an incident review.
Troubleshooting
# Dashboard pod not starting
kubectl describe pod -n kubernetes-dashboard -l k8s-app=kubernetes-dashboard
# Confirm metrics-server is present for usage graphs
kubectl get deployment metrics-server -n kube-system
# Token generation failing
kubectl auth can-i create tokens --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer
“Forbidden” errors inside the Dashboard UI almost always mean the RBAC binding doesn’t cover the resource or verb you’re trying to view/use — check kubectl auth can-i list pods --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer -n <namespace> to confirm.
Common Mistakes
- Binding the Dashboard’s ServiceAccount to
cluster-admin“just to get it working,” then forgetting to lock it down before exposing it more broadly. - Exposing the Dashboard on a public-facing LoadBalancer or Ingress without any authentication layer in front of it — this has been the root cause of several real-world cluster compromises.
- Using long-lived Secret-based tokens instead of the TokenRequest API’s short-lived tokens.
- Sharing a single ServiceAccount token across an entire team, losing any meaningful audit trail of who did what.
Best Practices
- Default to read-only RBAC for Dashboard access; grant write access narrowly and only to people who need it.
- Never expose the Dashboard directly to the public internet — use
kubectl proxy/port-forwardfor individual access, or an internal-only Ingress behind SSO for team access. - Prefer short-lived tokens via
kubectl create tokenover static long-lived ServiceAccount secrets. - Tie access to individual IAM/OIDC identities rather than shared tokens, for proper audit trails.
- Keep the Dashboard itself updated — like any web application with cluster-level API access, unpatched versions are a real attack surface.
Summary
The Kubernetes Dashboard is a genuinely useful web UI for day-to-day cluster visibility, but its biggest risk isn’t the tool itself — it’s misconfigured access. Install it, bind it to a narrowly-scoped, read-only ServiceAccount by default, keep it off the public internet, and use kubectl proxy or an SSO-gated internal Ingress for access. On EKS, tying Dashboard RBAC to individual IAM identities via access entries gives you both convenience and a proper audit trail, which matters a lot more than it seems until the first time you need to know who changed what.
