> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/internal-developer-portal/3.0/use-idp/environment-management/tutorials/tutorial1.md).

# Tutorial 1 - Build an Ephemeral Developer Testing Environment

### Overview <a href="#overview" id="overview"></a>

This tutorial shows you how to eliminate PR testing bottlenecks by building a self-service ephemeral environment system. Instead of developers struggling over shared staging environments and manual cleanups, you will create automated environments that provision in 5 minutes and delete themselves after 24 hours.

#### The problem scenario <a href="#the-problem-scenario" id="the-problem-scenario"></a>

Your development team of 12 engineers ships a web application with 30+ pull requests per week. Currently, PR testing is painful:

* **2 shared "staging" environments** → developers wait 2-4 hours for availability
* **Manual environment creation** takes 30-45 minutes (create namespace, deploy services, configure DNS)
* **No cleanup process** → 15+ old test namespaces consuming resources and cost
* **Inconsistent setup** → each developer configures differently ("works on my machine")
* **QA bottleneck** → QA team can only test 1-2 PRs at a time

#### The end result <a href="#the-end-result" id="the-end-result"></a>

A complete self-service environment system where developers can:

* Click a button to get an isolated test environment (namespace + frontend + backend)
* Test their PR without waiting or manual setup
* Have the environment automatically deleted after 24 hours (TTL)

***

### What you will build <a href="#what-you-will-build" id="what-you-will-build"></a>

* IaCM workspace templates for Kubernetes namespace provisioning
* Provision and destroy pipelines for infrastructure lifecycle
* CD services for a web application (frontend + backend)
* Environment Blueprints combining infrastructure and services
* TTL based Ephemeral Environments

![](/files/wVLdc6mZm07XX9iof4WK)

### Before you begin <a href="#before-you-begin" id="before-you-begin"></a>

{% hint style="warning" %}
**MAKE SURE YOU FULFILL THE REQUIREMENTS**

Before proceeding with the tutorial steps, ensure you have completed the [prerequisites outlined](/internal-developer-portal/use-idp/environment-management/get-started.md#before-you-begin) in the [**Get Started with Environment Management**](/internal-developer-portal/use-idp/environment-management/get-started.md) guide:

* ☑ Required modules and feature flags enabled
* ☑ Connector and delegate configured
* ☑ Infrastructure requirements met
* ☑ Permissions set across IDP, CD, and platform resources

#### Knowledge prerequisites <a href="#knowledge-prerequisites" id="knowledge-prerequisites"></a>

* ☑ Basic understanding of Kubernetes concepts (namespaces, deployments)
* ☑ Familiarity with Harness CD services and pipelines
* ☑ Basic knowledge of Helm charts
  {% endhint %}

***

### Component breakdown <a href="#component-breakdown" id="component-breakdown"></a>

1. **IaCM Workspace Template** - Defines reusable infrastructure patterns. Here we use it for Kubernetes namespaces, in principle this can be used for any infrastructure resource using Terraform/OpenTofu
2. **CD Services** - Define the applications to deploy (frontend + backend)
3. **Pipelines** - Create/destroy the namespaces, frontend, backend. This can be used to deploy any infrastructure resources, services
4. **Environment Blueprint** - Orchestrates all components with proper dependencies
5. **TTL Configuration in Blueprint** - Automatic environment cleanup after specified time period

***

### Who benefits <a href="#who-benefits" id="who-benefits"></a>

* **Developers** - Focus completely on coding instant test environments, no manual cleanup
* **Platform Team** - Consistent environment configurations with clear audit trail (who created what, when)
* **QA Engineers** - Test multiple PRs in parallel with predictable, consistent testing conditions
* **Finance Team** - Reduction in test environment costs

***

### Step-by-step walkthrough <a href="#step-by-step-walkthrough" id="step-by-step-walkthrough"></a>

{% hint style="info" %}
Update variables (marked as `<your-*>`) in code snippets with relevant reference to your set up
{% endhint %}

#### Step 1: Create the namespace Terraform module <a href="#step-1-create-the-namespace-terraform-module" id="step-1-create-the-namespace-terraform-module"></a>

Create the Terraform code that the workspace will use. You can use an existing git repository or create a new one. Here we will use an existing repository in Harness Code Repository. In your Git repository at `terraform/k8s-namespace/`, create the following files:

```hcl
provider "google" {
  project = <your_project_name>
  region  = <your_region_name>
}

data "google_client_config" "default" {}

data "google_container_cluster" "gke_cluster" {
  name     = <your_cluster_name>
  location = <your_region_name>
}

provider "kubernetes" {
  host = "https://${data.google_container_cluster.gke_cluster.endpoint}"

  token = data.google_client_config.default.access_token

  cluster_ca_certificate = base64decode(
    data.google_container_cluster.gke_cluster.master_auth[0].cluster_ca_certificate
  )
}

resource "kubernetes_namespace_v1" "ephemeral" {
  metadata {
    name = "${var.name}"
  }
}
```

```hcl
variable "name" {
  description = "Name of the Kubernetes namespace to create"
  type        = string
}
```

```hcl
output "name" {
  description = "The name of the created namespace"
  value       = kubernetes_namespace_v1.ephemeral.metadata[0].name
}
```

Commit and push these files to your Git repository.

#### Step 2: Create the IaCM workspace template <a href="#step-2-create-the-iacm-workspace-template" id="step-2-create-the-iacm-workspace-template"></a>

The workspace template defines the infrastructure pattern for creating Kubernetes namespaces.

1. Go to **Project Settings** and under **Project-level Resources**, choose **Templates**. You can also use templates at Account and Org level.
2. Create a new Template of type **Infra Workspace**.
3. Configure the template:

   * **Name:** `k8s-namespace-template`
   * **Version:** `v1`
   * **Description:** `Creates a Kubernetes namespace for ephemeral environments`
   * **Terraform/OpenTofu Version:** `1.5.6` (or your preferred version)

   ![](/files/0jrHc1XcqIH4rNjqvmnt)
4. In the **Repository** section, choose Harness Code Repository and:
   * **Repository:** Your infrastructure repository.
   * **Branch:** `main`
   * **Folder Path:** `terraform/k8s-namespace` (where your Terraform code resides)
5. In the **Variables** section, add:
   * An overridable Terraform variable:
     * `name` (string, no default) - The namespace to create
   * Your GCP connector with the required permission to create/update namespaces in your cluster
6. Click **Save** to create the template

Note the identifiers:

* **Template Name:** `k8s-namespace-template`
* **Template Version:** `v1`

You will need these in [Step 10](#step-10-create-the-environment-blueprint) when creating the Environment Blueprint.

#### Step 3: Create the IaCM provision pipeline <a href="#step-3-create-the-iacm-provision-pipeline" id="step-3-create-the-iacm-provision-pipeline"></a>

This pipeline creates the namespace when an environment is provisioned. In principle, you can use the pipeline to create workspaces for any resource, so long as the template has its terraform code.

1. Navigate to **Infrastructure as Code** module, go to **Pipelines** and hit **Create new pipeline** with name `provision-namespace-pipeline`
2. Select **Inline** storage type

   ![](/files/QdjerD5OKBzDX2GS3tC2)
3. Switch to **YAML** view and paste the following. Note that you can update the infrastructure, stages or even use existing pipelines here.

```yaml
pipeline:
  name: provision-namespace-pipeline
  identifier: provision_namespace_pipeline
  description: Pipeline used for provisioning EM infra
  projectIdentifier: <your-project-id>
  orgIdentifier: <your-org-id>
  tags: {}
  stages:
    - stage:
        name: Provision Namespace
        identifier: provision_namespace
        description: Creates a Kubernetes namespace using IaCM workspace
        type: IACM
        spec:
          workspace: <+input>
          platform:
            os: Linux
            arch: Amd64
          runtime:
            type: Cloud
            spec: {}
          execution:
            steps:
              - step:
                  type: IACMTerraformPlugin
                  name: Initialize
                  identifier: init
                  timeout: 5m
                  spec:
                    command: init
              - step:
                  type: IACMTerraformPlugin
                  name: Plan
                  identifier: plan
                  timeout: 5m
                  spec:
                    command: plan
              - step:
                  type: IACMTerraformPlugin
                  name: Apply Infrastructure
                  identifier: apply
                  timeout: 10m
                  spec:
                    command: apply
        tags: {}
```

4. Click **Save** to create the pipeline

   ![](/files/EOUtzpjHskCmq1igES7e)

Note the identifier:

* **Pipeline identifier:** `provision_namespace_pipeline`

#### Step 4: Create the destroy pipeline <a href="#step-4-create-the-destroy-pipeline" id="step-4-create-the-destroy-pipeline"></a>

This pipeline cleans up the namespace when the environment is deleted or TTL expires.

1. Create a new pipeline named `destroy-namespace-pipeline`
2. Use this YAML:

```yaml
pipeline:
  name: destroy-namespace-pipeline
  identifier: destroy_namespace_pipeline
  projectIdentifier: <your-project-id>
  orgIdentifier: <your-org-id>
  tags: {}
  stages:
    - stage:
        name: Destroy Namespace
        identifier: destroy_namespace
        description: Destroys the Kubernetes namespace and all resources
        type: IACM
        spec:
          workspace: <+input>
          platform:
            os: Linux
            arch: Amd64
          runtime:
            type: Cloud
            spec: {}
          execution:
            steps:
              - step:
                  type: IACMTerraformPlugin
                  name: init
                  identifier: init
                  timeout: 10m
                  spec:
                    command: init
              - step:
                  type: IACMTerraformPlugin
                  name: planDestroy
                  identifier: planDestroy
                  timeout: 10m
                  spec:
                    command: plan-destroy
              - step:
                  type: IACMTerraformPlugin
                  name: destroy
                  identifier: destroy
                  timeout: 1h
                  spec:
                    command: destroy
        tags: {}
```

3. Click **Save** to create the destroy pipeline

Note the identifier:

* **Pipeline identifier:** `destroy_namespace_pipeline`

#### Step 5: Create CD services <a href="#step-5-create-cd-services" id="step-5-create-cd-services"></a>

Now we will create two CD services for our sample application with the help of 2 toy nginx helm charts available here.

**Frontend service**

1. Navigate to **Continuous Delivery** → **Services**
2. Click **+ New Service**
3. Configure:
   * **Name:** `ephemeral-frontend`
   * **Description:** `React frontend for ephemeral testing`
4. Select **Inline** storage type

   ![](/files/qYcVo7cLAGfrXPnvTMSz)
5. In the **Service Definition:**
   * **Deployment Type:** `Native Helm`
   * **Manifests:** Click Add Manifest
     * **Manifest Type:** `Helm Chart`
     * **Manifest Source:** `Github` and select your Git connector or create a new one.
     * **Manifest Identifier:** `helm-frontend`
     * **Repository Name:**
     * **Git Fetch Type:** `Latest from Branch`
     * **Branch:** `main`
     * **Chart Path:** `/frontend`
     * **Helm Version:** `V3`
6. Click **Save** to create the frontend service

**Backend service**

1. Create another service named `ephemeral-backend` and follow the same steps as above.
2. **Service Definition:**
   * **Deployment Type:** `Native Helm`
   * **Manifests:** Click Add Manifest
     * **Manifest Type:** `Helm Chart`
     * **Manifest Source:** `Github` and select your Git connector or create a new one.
     * **Manifest Identifier:** `helm-backend`
     * **Repository Name:** `<your-repository-name>` (e.g., `demo`)
     * **Git Fetch Type:** `Latest from Branch`
     * **Branch:** `main`
     * **Chart Path:** `/backend`
     * **Helm Version:** `V3`
3. Click **Save** to create the backend service

Note the identifiers:

* **Frontend service:** `ephemeral-frontend`
* **Backend service:** `ephemeral-backend`

#### Step 6: Create a CD environment and infrastructure definition <a href="#step-6-create-a-cd-environment-and-infrastructure-definition" id="step-6-create-a-cd-environment-and-infrastructure-definition"></a>

The CD environment defines where your services will be deployed. We will create an environment with an infrastructure definition pointing to your Kubernetes cluster.

**Create the environment**

1. Navigate to **Continuous Delivery** → **Environments**
2. Click **+ New Environment**
3. Configure the environment:
   * **Name:** `ephemeral-env`
   * **Description:** `Environment for ephemeral testing deployments`
   * **Environment Type:** Select `Pre-Production`
4. Choose **Inline** as the store type
5. Click **Save** to create the environment

**Create the infrastructure definition**

1. In the newly created environment, switch to the **Infrastructure Definitions** tab
2. Click **+ Infrastructure Definition**
3. Configure the infrastructure:
   * **Name:** `ephemeral-infra`
   * **Deployment Type:** Select `Native Helm`
4. Under **Infrastructure Type**, choose based on your cloud provider. In our example, we will use **Direct Connection** for vendor-agnostic Kubernetes
5. Configure the cluster details:
   * **Connector:** Select your Kubernetes or cloud provider connector (or create a new one)
   * **Namespace:** Mark as runtime input.
   * **Release Name:** Set to `<+service.name>` (this ensures each service gets a unique Helm release name)
6. Click **Save** to create the infrastructure definition

Note the identifiers:

* **Environment identifier:** `ephemeral_env`
* **Infrastructure definition identifier:** `ephemeral_infra`

You will need these identifiers in [Step 10](#step-10-create-the-environment-blueprint) when creating the Environment Blueprint.

#### Step 7: Create service deployment and delete pipelines <a href="#step-7-create-service-deployment-and-delete-pipelines" id="step-7-create-service-deployment-and-delete-pipelines"></a>

Create deployment and delete pipelines for both services. Note that you can update this pipeline based on your needs or even use your existing pipelines here.

```yaml
pipeline:
  name: deploy-service
  identifier: deploy_service
  projectIdentifier: <your-project-id>
  orgIdentifier: <your-org-id>
  tags: {}
  stages:
    - stage:
        name: Deploy
        identifier: Deploy
        description: ""
        type: Deployment
        spec:
          deploymentType: NativeHelm
          service:
            serviceRef: <+input>
            serviceInputs: <+input>
          environment:
            environmentRef: <+input>
            deployToAll: false
            environmentInputs: <+input>
            serviceOverrideInputs: <+input>
            infrastructureDefinitions: <+input>
          execution:
            steps:
              - step:
                  name: Helm Deployment
                  identifier: helmDeployment
                  type: HelmDeploy
                  timeout: 10m
                  spec:
                    skipDryRun: false
            rollbackSteps:
              - step:
                  name: Helm Rollback
                  identifier: helmRollback
                  type: HelmRollback
                  timeout: 10m
                  spec: {}
        tags: {}
        failureStrategies:
          - onFailure:
              errors:
                - AllErrors
              action:
                type: StageRollback
```

```yaml
pipeline:
  name: delete-service
  identifier: delete_service
  projectIdentifier: <your-project-id>
  orgIdentifier: <your-org-id>
  tags: {}
  stages:
    - stage:
        name: uninstall
        identifier: uninstall
        description: ""
        type: Deployment
        spec:
          deploymentType: NativeHelm
          service:
            serviceRef: <+input>
            serviceInputs: <+input>
          environment:
            environmentRef: <+input>
            deployToAll: false
            environmentInputs: <+input>
            serviceOverrideInputs: <+input>
            infrastructureDefinitions: <+input>
          execution:
            steps:
              - step:
                  type: HelmDelete
                  name: HelmDelete_1
                  identifier: HelmDelete_1
                  spec:
                    dryRun: false
                    releaseName: <+service.name>
                  timeout: 10m
            rollbackSteps:
              - step:
                  name: Helm Rollback
                  identifier: helmRollback
                  type: HelmRollback
                  timeout: 10m
                  spec: {}
        tags: {}
        failureStrategies:
          - onFailure:
              errors:
                - AllErrors
              action:
                type: StageRollback
```

Note the identifiers:

* **Deploy pipeline:** `deploy_service`
* **Delete pipeline:** `delete_service`

You will need these in Step 9 when updating the Catalog entities.

#### Step 8: Enable CD auto-discovery to register catalog entities <a href="#step-8-enable-cd-auto-discovery-to-register-catalog-entities" id="step-8-enable-cd-auto-discovery-to-register-catalog-entities"></a>

CD Auto-discovery will automatically discover and import CD services as Catalog components in IDP. For more information on how to do this, go to [Catalog Auto-Discovery with Harness CD Services](/internal-developer-portal/3.0/use-idp/software-catalog/create-entity/catalog-discovery/harness-cd.md)

#### Step 9: Update catalog entities with deployment pipelines <a href="#step-9-update-catalog-entities-with-deployment-pipelines" id="step-9-update-catalog-entities-with-deployment-pipelines"></a>

Catalog entities should have the necessary deployment pipelines in the Catalog Info YAML.

1. Navigate to **Internal Developer Portal** → **Catalog**
2. Search for the entities `ephemeral-frontend` and `ephemeral-backend`
3. Click to **Edit**, switch to YAML and paste the following under `spec`:

```yaml
deploymentPipelines:
  - identifier: deploy_service
  - identifier: delete_service
    type: uninstall
```

#### Step 10: Create the environment blueprint <a href="#step-10-create-the-environment-blueprint" id="step-10-create-the-environment-blueprint"></a>

Now we will assemble all components into an environment blueprint that developers can use.

1. Navigate to **Internal Developer Portal** → **Environments**
2. Click **Create** → **Environment Blueprint**
3. **Name:** `ephemeral-testing-blueprint`, **Version:** `1.0.0`, **Owner:** `<Your team>`
4. Click **Configure Environment Blueprint** and paste YAML:

```yaml
apiVersion: harness.io/v1
kind: EnvironmentBlueprint
type: ephemeral
name: Ephemeral Testing Environment
identifier: ephemeral_testing_blueprint
owner: <your-team-name>
spec:
  ttl:
    kind: fixed
    default: 24h
  entities:
    - identifier: namespace
      backend:
        type: HarnessIACM
        values:
          workspace: Namespace-${{env.config.name}}
        steps:
          create:
            template: k8s-namespace-template  # Workspace Template created in Step 2
            version: v1
          apply:
            pipeline: provision_namespace_pipeline  # Provision pipeline from Step 3
            variables:
              name: ephemeral-${{env.config.name}}
          destroy:
            pipeline: destroy_namespace_pipeline  # Destroy pipeline from Step 4
            variables:
              name: ephemeral-${{env.config.name}}
          delete: {}
          pause: {}
          resume: {}
    - identifier: ephemeral_frontend
      interface:
        dependencies:
          - identifier: namespace
        inputs:
          version:
            type: string
            default: 1.0.0
          replicas:
            type: integer
            default: 1
      backend:
        type: Catalog
        values:
          identifier: ephemeral-frontend  # Frontend service from Step 5
          variables:
            replicas: ${{entity.config.replicas}}
            version: ${{entity.config.version}}
          environment:
            identifier: <your-environment-id>  # CD Environment from Step 6
          infra:
            identifier: <your-infra-definition-id>  # Infrastructure Definition from Step 6
            namespace: ${{dependencies.namespace.output.name}}
    - identifier: ephemeral_backend
      interface:
        dependencies:
          - identifier: namespace
        inputs:
          version:
            type: string
            default: 1.0.0
          replicas:
            type: integer
            default: 1
      backend:
        type: Catalog
        values:
          identifier: ephemeral-backend  # Backend service from Step 5
          variables:
            version: ${{entity.config.version}}
            replicas: ${{entity.config.replicas}}
          environment:
            identifier: <your-environment-id>  # CD Environment from Step 6
          infra:
            identifier: <your-infra-definition-id>  # Infrastructure Definition from Step 6
            namespace: ${{dependencies.namespace.output.name}}
  ownedBy:
    - <your-owner-name>
  inputs:
    name:
      type: string
      default: demo
```

![](/files/1Myc93Cn75CeolHcRHPS)

5. Click **Save Blueprint**

#### Step 11: Provision an ephemeral environment <a href="#step-11-provision-an-ephemeral-environment" id="step-11-provision-an-ephemeral-environment"></a>

Now let us create an environment instance from the blueprint.

1. Navigate to **Internal Developer Portal** → **Environments**
2. Click **Create** → **Environment**
3. Select **Ephemeral Testing Environment** blueprint

   ![](/files/HVA8XFGwEcYICFHdXHyY)
4. Fill in the configuration:

   * **Name:** `pr-456`
   * For each service:
     * **Replicas:** `2`
     * **Version:** `1.0.0`

   ![](/files/riY3i9UIExR1iJHIiEkX)
5. Click **Create Environment** and then **Start Environment**
6. The provisioning process will begin:
   * **Namespace Creation:** IaCM runs the provision pipeline
   * **Frontend Deployment:** CD deploys the frontend service
   * **Backend Deployment:** CD deploys the backend service
7. Once complete, you will see the environment dashboard with **Environment status:** `Online`

**Congratulations! You have successfully created an ephemeral environment system.**

***

### Recommended reading <a href="#recommended-reading" id="recommended-reading"></a>

* [Environment Management Overview | Harness Developer Hub](/internal-developer-portal/use-idp/environment-management/overview.md)
* [Get Started with Environment Management | Harness Developer Hub](/internal-developer-portal/use-idp/environment-management/get-started.md)
* [Workspace Templates | Harness Developer Hub](/infrastructure-as-code-management/platform/workspaces/workspace-templates.md)
