How to Set Up Kubernetes Dashboard

How to Set Up Kubernetes Dashboard

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:

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

Best Practices

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.

References

Exit mobile version