The kubectl command kubectl apply is failing because the Kubernetes API server is reporting that an environment already exists with the same name that your GitLab CI job is trying to create.

This conflict typically arises when GitLab CI attempts to deploy an application to a Kubernetes environment that was previously created but not properly cleaned up, or when multiple CI/CD pipelines try to manage the same environment concurrently. The kubectl apply command, used by GitLab CI to manage Kubernetes resources, relies on unique names for these resources. When a duplicate name is detected, the API server rejects the creation request, leading to the "environment already exists" error.

Here are the most common causes and their fixes:

1. Stale Kubernetes Environment Resources

Diagnosis: The most frequent culprit is a Kubernetes Environment or Deployment resource that was left behind from a previous, potentially failed, pipeline run.

Command to Check:

kubectl get environments.gitlab.io,deployments.apps -n <your-gitlab-namespace> --show-labels

Replace <your-gitlab-namespace> with the Kubernetes namespace where GitLab is configured to manage its environments (often gitlab-runner or a project-specific namespace). Look for entries with labels indicating they are associated with your project and branch.

Fix: Delete the stale Kubernetes resource.

kubectl delete environment <environment-name> -n <your-gitlab-namespace>
# OR if it's a deployment:
kubectl delete deployment <deployment-name> -n <your-gitlab-namespace>

The <environment-name> or <deployment-name> will be visible in the output of the get command and often follows a pattern like project-slug-branch-name.

Why it works: This removes the conflicting resource from Kubernetes, allowing GitLab CI to create a new one with the same name.

2. Concurrent Pipeline Runs for the Same Branch

Diagnosis: If your CI/CD pipeline is configured to run on every commit and you have multiple commits pushed in quick succession, or if a pipeline is taking a long time, two CI jobs might try to create the same environment simultaneously.

Check: Review your GitLab CI pipeline history for the specific branch. See if multiple jobs for that branch were running concurrently. Also, check your .gitlab-ci.yml for rules or only keywords that might trigger pipelines too frequently for the same environment.

Fix:

  • Implement Pipeline Concurrency Limits: In your .gitlab-ci.yml, you can limit the number of concurrent pipelines for a branch:
    workflow:
      rules:
        - if: $CI_COMMIT_BRANCH
          when: on_success
      # Limit to one active pipeline per branch
      pipeline_concurrency:
        limit: 1
        key: "$CI_COMMIT_BRANCH"
    
  • Manual Triggering or Scheduled Pipelines: For long-running deployments, consider manual triggers or scheduled pipelines to avoid conflicts.

Why it works: Limiting concurrency ensures that only one pipeline can attempt to create or update an environment for a given branch at any given time, preventing race conditions.

3. Incorrect Environment Naming Convention

Diagnosis: The way GitLab CI generates environment names might be colliding with existing Kubernetes resources if the naming convention is not consistent or if manual resources were created with similar names. GitLab typically uses <project-slug>-<branch-name> for environments.

Check: Examine the environment: section in your .gitlab-ci.yml and compare the generated name with existing Kubernetes resources.

deploy_staging:
  stage: deploy
  script:
    - kubectl apply -f k8s/staging/
  environment:
    name: staging/$CI_COMMIT_REF_SLUG
    url: http://staging.example.com

Here, $CI_COMMIT_REF_SLUG is used to create a unique environment name. If this slug generation is faulty or if manual resources were created like myproject-main and the CI job tries to create myproject-main, you’ll have a conflict.

Fix: Ensure your .gitlab-ci.yml uses a robust naming convention for environments that guarantees uniqueness. Using predefined CI/CD variables like $CI_PROJECT_SLUG and $CI_COMMIT_REF_SLUG is highly recommended. If manual resources exist, rename them or delete and re-create them using the CI/CD convention.

Why it works: A consistent and unique naming strategy prevents GitLab CI from attempting to create resources that already exist, whether created by the CI/CD system or manually.

4. Helm Chart Issues

Diagnosis: If you are using Helm to manage your Kubernetes deployments, Helm’s release management can sometimes lead to conflicts if not handled correctly, especially when uninstalling or upgrading. A Helm release name might persist even after the associated Kubernetes resources are gone, or vice-versa.

Check:

helm list -n <your-gitlab-namespace>
kubectl get releases -n <your-gitlab-namespace> # If using Helm v2

Look for Helm releases that might correspond to your GitLab environment.

Fix: Uninstall the conflicting Helm release.

helm uninstall <release-name> -n <your-gitlab-namespace>

If the Helm release is not found but the Kubernetes resources exist, you might need to manually delete the Kubernetes resources as described in cause #1.

Why it works: Helm manages releases as distinct entities. If a release is left in a problematic state, uninstalling it cleanly removes the Helm-specific tracking, allowing a new deployment (or a re-installation with the same name) to proceed.

5. GitLab Runner Kubernetes Executor Configuration

Diagnosis: The Kubernetes namespace where the GitLab Runner is configured to create resources might be misconfigured or shared across projects in a way that leads to naming collisions.

Check: Review your GitLab Runner configuration for the Kubernetes executor. The namespace setting in config.toml or in the Kubernetes Secret/ConfigMap used by the runner is critical.

[[runners]]
  name = "my-k8s-runner"
  url = "https://gitlab.com/"
  token = "..."
  executor = "kubernetes"
  [runners.kubernetes]
    namespace = "gitlab-runner" # Ensure this is appropriate
    image = "docker:latest"
    privileged = true

Ensure that the namespace is correctly specified and that it’s not causing unintended resource sharing or conflicts between different projects or branches.

Fix: Adjust the namespace in the runner’s configuration to a more isolated or appropriate one. You might need to create a dedicated namespace per project or team if conflicts are rampant.

Why it works: By assigning runners to specific, less contended namespaces, you reduce the likelihood of concurrent jobs from different projects or branches attempting to create resources with identical names within the same Kubernetes cluster.

6. GitLab Environment Cleanup Configuration

Diagnosis: GitLab CI’s automatic environment cleanup might not be configured correctly or might be disabled, leading to resources persisting after a branch is deleted or a pipeline is completed.

Check: In your .gitlab-ci.yml, look for environment: cleanup directives.

deploy_production:
  stage: deploy
  script:
    - kubectl apply -f k8s/production/
  environment:
    name: production
    url: http://prod.example.com
    # This section is key for cleanup
    on_stop: stop_production

And the corresponding stop_production job:

stop_production:
  stage: cleanup
  script:
    - kubectl delete -f k8s/production/ || echo "No resources to delete"
  environment:
    name: production
    action: stop

Fix: Ensure you have a corresponding stop environment job defined in your .gitlab-ci.yml that is triggered when the pipeline finishes or when the branch is deleted. Configure on_stop correctly.

Why it works: Explicitly defining a cleanup job ensures that associated Kubernetes resources are removed when an environment is no longer needed, preventing future "environment already exists" errors for that specific environment.

After addressing these, the next error you might encounter is a ResourceAlreadyExists conflict if you are using custom resource definitions (CRDs) that have their own specific naming requirements or if a different type of Kubernetes object (like a Service or Ingress) is also named identically and causing a conflict.

Want structured learning?

Take the full Gitlab-ci course →