Deploying to Kubernetes

Learn how to connect a Kubernetes cluster to GitLab using the agent, run kubectl and Helm commands from CI/CD pipelines, configure KUBE_CONTEXT for environments, and choose between CI/CD push deployments and GitOps with Flux.

Kubernetes GitOps GitLab CI/CD
How GitLab Connects to Kubernetes

GitLab does not talk to Kubernetes directly. Instead, it uses an agent — a small process installed inside your cluster that maintains a bidirectional connection to GitLab. The agent is registered in a GitLab project, receives configuration from a YAML file in that project, and exposes the Kubernetes API to GitLab CI/CD jobs through a secure proxy. This design means your cluster can sit behind a firewall or NAT, and GitLab can still deploy to it without exposing the Kubernetes API to the public internet [citation:19].

Once the agent is connected, GitLab CI/CD jobs can run kubectl and helm commands against the cluster. The key is the KUBE_CONTEXT variable, which tells the job which agent connection to use. GitLab automatically injects a kubeconfig into every job that has access to the agent, and setting KUBE_CONTEXT selects the right cluster from that kubeconfig. A single project can deploy to multiple clusters by defining multiple KUBE_CONTEXT variables scoped to different environments [citation:1][citation:4].

GitLab supports two deployment workflows. The CI/CD workflow is push-based: a pipeline job runs kubectl apply or helm upgrade, and the changes are applied immediately. The GitOps workflow is pull-based: a Git repository is the source of truth, and Flux (running in the cluster) continuously reconciles the cluster state with the repository. GitLab recommends GitOps for production because it has a stronger security model and provides automatic drift remediation, while the CI/CD workflow is useful for pipeline-driven processes and migration scenarios [citation:19].

Core Principle: The agent is the bridge. Install it once per cluster, register it in a project, and then any authorized CI/CD job can use kubectl or helm against that cluster using KUBE_CONTEXT.

GitLab Agent

Runs in the cluster, connects to GitLab via KAS. Enables CI/CD access to the Kubernetes API without exposing it publicly.

KUBE_CONTEXT

Selects which agent connection to use. Format: path/to/project:agent-name. Can be scoped per environment.

CI/CD Workflow

Push-based. Pipeline runs kubectl/helm. Simpler but weaker security. Not recommended for production.

GitOps (Flux)

Pull-based. Flux reconciles cluster from Git/OCI. Recommended for production. Automatic drift remediation.

Agent Authorization

Projects/groups must be authorized in the agent config (ci_access). Controls which pipelines can use the agent.

RBAC

The agent's service account permissions determine what kubectl can do. Default is cluster-admin; restrict for production.
Installing and Registering the Agent

Installing the agent is a three-step process: create a configuration file in your repository, register the agent in GitLab to get an access token, and install the agent in your cluster using Helm. The configuration file lives at .gitlab/agents//config.yaml in the default branch of the project where you register the agent. It can be empty at first, but it's where you'll later define ci_access rules to authorize other projects and user_access rules for dashboard access [citation:6].

Registration happens in the GitLab UI: navigate to Operate → Kubernetes clusters, click "Connect a cluster," and choose an agent name. GitLab generates an access token and a Helm install command. The token is shown only once — store it securely. The Helm command installs the agent into a namespace (by default gitlab-agent) and connects it to GitLab's KAS (Kubernetes Agent Server) endpoint [citation:3][citation:6].

The default Helm installation grants the agent's service account cluster-admin rights. This is convenient for getting started but dangerous in production: any pipeline authorized to use the agent can do anything in the cluster. For production, customize the Helm installation to use a service account with only the permissions your deployments need — typically CRUD on Deployments, Services, ConfigMaps, and Secrets in specific namespaces [citation:3][citation:11].

# Step 1: Create agent configuration file # Path: .gitlab/agents/my-agent/config.yaml # (Can be empty initially) # Step 2: Register agent in GitLab UI # Operate > Kubernetes clusters > Connect a cluster # Enter agent name: my-agent # Click "Create and register" # Copy the token and Helm command # Step 3: Install agent with Helm (from the command GitLab gave you) helm repo add gitlab https://charts.gitlab.io helm repo update helm upgrade --install my-agent gitlab/gitlab-agent \ --namespace gitlab-agent-my-agent \ --create-namespace \ --set image.tag=v17.10.1 \ --set config.token=glagent-xxxxxxxxxxxx \ --set config.kasAddress=wss://kas.gitlab.com # For self-managed GitLab: # --set config.kasAddress=wss://gitlab.example.com/-/kubernetes-agent/ # Production: restrict RBAC instead of cluster-admin helm upgrade --install my-agent gitlab/gitlab-agent \ --namespace gitlab-agent-my-agent \ --create-namespace \ --set config.token=$AGENT_TOKEN \ --set config.kasAddress=wss://kas.gitlab.com \ --set serviceAccount.create=false \ --set serviceAccount.name=gitlab-deployer \ --set rbac.create=false # Then create a ServiceAccount + Role + RoleBinding manually # granting only the permissions your deployments need # Verify connection # Operate > Kubernetes clusters # Status should show "Connected" # Agent config file example (authorize a project) # .gitlab/agents/my-agent/config.yaml ci_access: projects: - id: my-group/my-application # Or authorize a whole group: ci_access: groups: - id: my-group # Or all projects on the instance (admin only): ci_access: instance: {}
Agent Security:
  • Token is sensitive: A bad actor with the agent token can access source code and potentially the cluster
  • Default is cluster-admin: Restrict RBAC for production
  • Authorize narrowly: Use ci_access to allow only the projects that need to deploy
  • One agent per cluster: Do not install multiple agents in the same cluster unless you have a specific multi-tenancy need
CI/CD Deployment with kubectl

Once the agent is connected and authorized, deploying with kubectl from a GitLab CI/CD job is straightforward. The job needs a container image with kubectl installed (like bitnami/kubectl or alpine/k8s), and it needs to set KUBE_CONTEXT to the agent's context path. GitLab injects a kubeconfig into the job environment automatically; setting the context selects the right cluster from that kubeconfig. After that, kubectl apply, kubectl set image, and kubectl rollout status work exactly as they would locally [citation:4][citation:12].

The typical deployment pattern is to build an image, push it to the GitLab Container Registry, and then deploy it using kubectl set image or kubectl apply with the new image tag. For manifest-based deployments, you can use sed to substitute the image placeholder with the actual image reference, or use Kustomize to patch the image tag. For a more robust approach, kubectl apply -k with Kustomize overlays lets you manage environment-specific configuration cleanly [citation:14].

It's important to use kubectl rollout status after applying changes. This command blocks until the deployment is complete or fails, which gives the pipeline an accurate success/failure signal. Without it, the job succeeds as soon as the API accepts the request, even if the pods crash immediately afterward. For rollbacks, GitLab's environment tracking (covered in the previous guide) lets you redeploy a previous commit, which re-runs the job with the older image tag.

# CI/CD deployment with kubectl deploy_production: stage: deploy image: bitnami/kubectl:latest variables: KUBE_CONTEXT: my-group/my-agent-project:my-agent before_script: # KUBE_CONTEXT selects the agent from the injected kubeconfig - kubectl config use-context $KUBE_CONTEXT script: # Update the deployment image - kubectl set image deployment/myapp \ myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA \ -n production # Wait for rollout to complete - kubectl rollout status deployment/myapp -n production --timeout=5m environment: name: production url: https://example.com rules: - if: $CI_COMMIT_BRANCH == "main" when: manual # Manifest-based deployment with sed deploy_manifest: stage: deploy image: bitnami/kubectl:latest variables: KUBE_CONTEXT: my-group/my-agent-project:my-agent before_script: - kubectl config use-context $KUBE_CONTEXT script: # Replace image placeholder in manifest - sed -i "s|IMAGE_PLACEHOLDER|$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA|g" k8s/deployment.yaml - kubectl apply -f k8s/deployment.yaml -n production - kubectl rollout status deployment/myapp -n production # Kustomize-based deployment deploy_kustomize: stage: deploy image: bitnami/kubectl:latest variables: KUBE_CONTEXT: my-group/my-agent-project:my-agent before_script: - kubectl config use-context $KUBE_CONTEXT script: - cd k8s/overlays/production - kustomize edit set image myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - kubectl apply -k . - kubectl rollout status deployment/myapp -n production # KUBE_CONTEXT format: # path/to/agent/project:agent-name # Example: my-group/my-agent-project:my-agent # For environment-scoped KUBE_CONTEXT (multiple clusters): # Settings > CI/CD > Variables # Variable: KUBE_CONTEXT # Value: my-group/my-agent-project:production-agent # Environment scope: production # # Variable: KUBE_CONTEXT # Value: my-group/my-agent-project:staging-agent # Environment scope: staging
kubectl Deployment Best Practices: