> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 4. Service-to-Service Communication

> Configure internal firewall rules to enable secure service-to-service communication between workloads using automatic mTLS encryption.

## Overview

This quickstart demonstrates how workloads communicate internally with automatic mTLS encryption. By default, workloads are isolated and reject internal traffic. You'll configure firewall rules to enable secure service-to-service communication.

**What you'll accomplish:**

* Deploy a new workload that communicates with an existing service
* Observe the default deny behavior
* Configure internal firewall rules to allow communication
* Verify secure service-to-service communication

## Prerequisites

* Completed [Quickstart 1](/quickstart/quick-start-1-deploy-workload) with a running [workload](/guides/create-workload)
* [Permissions](/guides/policy) to create workloads in your org

## How Internal Communication Works

Workloads communicate using internal endpoints that follow this pattern:

```text theme={null}
http://WORKLOAD_NAME.GVC_NAME.cpln.local:PORT
```

All internal traffic is automatically encrypted with mTLS. No certificate management is required.

So far, you've created a GVC (`quickstart-gvc`) and deployed a workload (`hello-world`). Now you'll create a new workload that communicates with the existing `hello-world` workload.

<Tabs>
  <Tab title="Console UI">
    ## Step 1: Create the Caller Workload

    <Steps>
      <Step title="Navigate to Workloads">
        Click `Workloads` in the left menu, then click `New`.
      </Step>

      <Step title="Configure workload basics">
        1. Name: `caller`
        2. Make sure `quickstart-gvc` is selected in the GVC dropdown
      </Step>

      <Step title="Configure container">
        1. Click `Containers` in the left pane
        2. Select `External` and enter the image: `cplnquickstarts/service-to-service-quick-start:1.1`
        3. Under `Ports`, set Protocol to `http` and Number to `8080`
      </Step>

      <Step title="Configure firewall and create">
        1. Click `Firewall` in the left pane
        2. Click `Make Public`
        3. Click `Create`
      </Step>
    </Steps>

    ## Step 2: Observe Default Deny Behavior

    <Steps>
      <Step title="Wait for workload to be ready">
        The `caller` workload shows `Ready` in Workload Health.
      </Step>

      <Step title="Open the caller workload">
        Navigate to the `caller` workload and click `Open` next to the Global Endpoint.
      </Step>

      <Step title="Test internal call">
        Add this query parameter to the caller URL:

        ```text theme={null}
        ?url=http://hello-world.quickstart-gvc.cpln.local:8080
        ```

        The request will time out and fail because the `hello-world` workload blocks internal traffic by default.
      </Step>
    </Steps>

    ## Step 3: Configure Internal Firewall

    <Steps>
      <Step title="Navigate to hello-world workload">
        Click `Workloads` in the left menu and select `hello-world`.
      </Step>

      <Step title="Configure firewall">
        1. Click `Firewall` in the left pane
        2. Under the `Internal` tab, select `Same GVC` for the Inbound Allow Type
        3. Click `Update`
      </Step>
    </Steps>

    ## Step 4: Verify Communication

    <Steps>
      <Step title="Wait for redeployment">
        The `hello-world` workload redeploys with the updated firewall rules after 1-2 minutes.
      </Step>

      <Step title="Test again">
        Refresh the caller page with the URL parameter. The `hello-world` workload now responds:

        ```text theme={null}
        Response from URL:

        ---
        Hello World!
        ---
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="CLI">
    ## Step 1: Create the Caller Workload

    ```bash theme={null}
    cpln workload create --name caller --gvc quickstart-gvc \
      --image cplnquickstarts/service-to-service-quick-start:1.1 \
      --port 8080 \
      --public
    ```

    ## Step 2: Test Default Deny

    Get the caller endpoint:

    ```bash theme={null}
    cpln workload get caller --gvc quickstart-gvc
    ```

    Open the endpoint from the table in your browser and add:

    ```text theme={null}
    ?url=http://hello-world.quickstart-gvc.cpln.local:8080
    ```

    The request will time out and fail because the `hello-world` workload blocks internal traffic by default.

    ## Step 3: Enable Internal Access

    Update the `hello-world` firewall configuration to allow internal traffic:

    ```bash theme={null}
    cpln workload update hello-world --gvc quickstart-gvc --set spec.firewallConfig.internal.inboundAllowType=same-gvc
    ```

    ## Step 4: Verify Communication

    Wait 1-2 minutes for redeployment, then refresh the caller page. The `hello-world` workload now responds:

    ```text theme={null}
    Response from URL:

    ---
    Hello World!
    ---
    ```
  </Tab>

  <Tab title="Terraform">
    ## Step 1: Add the Caller Workload

    Add to your existing `main.tf` from Quickstart 1:

    ```hcl theme={null}
    # Caller workload
    resource "cpln_workload" "caller" {
      gvc  = cpln_gvc.quickstart.name
      name = "caller"
      type = "standard"

      container {
        name   = "main"
        image  = "cplnquickstarts/service-to-service-quick-start:1.1"
        cpu    = "50m"
        memory = "128Mi"

        ports {
          protocol = "http"
          number   = 8080
        }
      }

      options {
        capacity_ai     = true
        timeout_seconds = 5

        autoscaling {
          metric      = "disabled"
          target      = 95
          min_scale   = 1
          max_scale   = 1
        }
      }

      firewall_spec {
        external {
          inbound_allow_cidr = ["0.0.0.0/0"]
        }
      }
    }

    output "caller_endpoint" {
      value = cpln_workload.caller.status[0].canonical_endpoint
    }
    ```

    Apply the configuration:

    ```bash theme={null}
    terraform apply
    ```

    ## Step 2: Test Default Deny

    Open the caller endpoint and add:

    ```text theme={null}
    ?url=http://hello-world.quickstart-gvc.cpln.local:8080
    ```

    The request will time out and fail because the `hello-world` workload blocks internal traffic by default.

    ## Step 3: Enable Internal Access

    Update your `cpln_workload.hello_world` resource to allow internal traffic. Add the `internal` block inside `firewall_spec`:

    ```hcl theme={null}
    firewall_spec {
      external {
        inbound_allow_cidr  = ["0.0.0.0/0"]
      }

      # Add internal firewall to allow traffic from same GVC
      internal {
        inbound_allow_type = "same-gvc"
      }
    }
    ```

    Apply the changes:

    ```bash theme={null}
    terraform apply
    ```

    ## Step 4: Verify Communication

    Wait 1-2 minutes for redeployment, then refresh the caller page. The `hello-world` workload now responds:

    ```text theme={null}
    Response from URL:

    ---
    Hello World!
    ---
    ```
  </Tab>

  <Tab title="Pulumi">
    ## Step 1: Add the Caller Workload

    Add to your existing Pulumi project from Quickstart 1:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // Caller workload
        const caller = new cpln.Workload("caller", {
          gvc: gvc.name,
          name: "caller",
          type: "standard",
          containers: [
            {
              name: "main",
              image: "cplnquickstarts/service-to-service-quick-start:1.1",
              cpu: "50m",
              memory: "128Mi",
              ports: [{ protocol: "http", number: 8080 }],
            },
          ],
          options: {
            capacityAi: true,
            timeoutSeconds: 5,
            autoscaling: {
              metric: "disabled",
              target: 95,
              minScale: 1,
              maxScale: 1,
            },
          },
          firewallSpec: {
            external: {
              inboundAllowCidrs: ["0.0.0.0/0"],
            },
          },
        });

        export const callerEndpoint = caller.statuses.apply(
          (s) => s?.[0]?.canonicalEndpoint ?? "pending"
        );
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # Caller workload
        caller = cpln.Workload("caller",
            gvc=gvc.name,
            name="caller",
            type="standard",
            containers=[cpln.WorkloadContainerArgs(
                name="main",
                image="cplnquickstarts/service-to-service-quick-start:1.1",
                cpu="50m",
                memory="128Mi",
                ports=[cpln.WorkloadContainerPortArgs(
                    protocol="http",
                    number=8080,
                )],
            )],
            options=cpln.WorkloadOptionsArgs(
                capacity_ai=True,
                timeout_seconds=5,
                autoscaling=cpln.WorkloadOptionsAutoscalingArgs(
                    metric="disabled",
                    target=95,
                    min_scale=1,
                    max_scale=1,
                ),
            ),
            firewall_spec=cpln.WorkloadFirewallSpecArgs(
                external=cpln.WorkloadFirewallSpecExternalArgs(
                    inbound_allow_cidrs=["0.0.0.0/0"],
                ),
            ))

        pulumi.export("caller_endpoint", caller.statuses[0].canonical_endpoint)
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // Caller workload
        caller, err := cpln.NewWorkload(ctx, "caller", &cpln.WorkloadArgs{
        	Gvc:  gvc.Name,
        	Name: pulumi.String("caller"),
        	Type: pulumi.String("standard"),
        	Containers: cpln.WorkloadContainerArray{
        		&cpln.WorkloadContainerArgs{
        			Name:   pulumi.String("main"),
        			Image:  pulumi.String("cplnquickstarts/service-to-service-quick-start:1.1"),
        			Cpu:    pulumi.String("50m"),
        			Memory: pulumi.String("128Mi"),
        			Ports: cpln.WorkloadContainerPortArray{
        				&cpln.WorkloadContainerPortArgs{
        					Protocol: pulumi.String("http"),
        					Number:   pulumi.Int(8080),
        				},
        			},
        		},
        	},
        	Options: &cpln.WorkloadOptionsArgs{
        		CapacityAi:     pulumi.Bool(true),
        		TimeoutSeconds: pulumi.Int(5),
        		Autoscaling: &cpln.WorkloadOptionsAutoscalingArgs{
        			Metric:   pulumi.String("disabled"),
        			Target:   pulumi.Int(95),
        			MinScale: pulumi.Int(1),
        			MaxScale: pulumi.Int(1),
        		},
        	},
        	FirewallSpec: &cpln.WorkloadFirewallSpecArgs{
        		External: &cpln.WorkloadFirewallSpecExternalArgs{
        			InboundAllowCidrs: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
        		},
        	},
        })
        if err != nil {
        	return err
        }

        ctx.Export("caller_endpoint", caller.Statuses.Index(pulumi.Int(0)).CanonicalEndpoint())
        ```
      </Tab>

      <Tab title="C#">
        ```csharp theme={null}
        // Caller workload
        var caller = new Workload("caller", new WorkloadArgs
        {
            Gvc = gvc.Name,
            Name = "caller",
            Type = "standard",
            Containers = new[]
            {
                new WorkloadContainerArgs
                {
                    Name = "main",
                    Image = "cplnquickstarts/service-to-service-quick-start:1.1",
                    Cpu = "50m",
                    Memory = "128Mi",
                    Ports = new[]
                    {
                        new WorkloadContainerPortArgs
                        {
                            Protocol = "http",
                            Number = 8080
                        }
                    }
                }
            },
            Options = new WorkloadOptionsArgs
            {
                CapacityAi = true,
                TimeoutSeconds = 5,
                Autoscaling = new WorkloadOptionsAutoscalingArgs
                {
                    Metric = "disabled",
                    Target = 95,
                    MinScale = 1,
                    MaxScale = 1
                }
            },
            FirewallSpec = new WorkloadFirewallSpecArgs
            {
                External = new WorkloadFirewallSpecExternalArgs
                {
                    InboundAllowCidrs = new[] { "0.0.0.0/0" }
                }
            }
        });
        ```

        Add to your return dictionary:

        ```csharp theme={null}
        ["caller_endpoint"] = caller.Statuses.Apply(s => s[0].CanonicalEndpoint)
        ```
      </Tab>
    </Tabs>

    Deploy the configuration:

    ```bash theme={null}
    pulumi up
    ```

    ## Step 2: Test Default Deny

    Open the caller endpoint and add:

    ```text theme={null}
    ?url=http://hello-world.quickstart-gvc.cpln.local:8080
    ```

    The request will time out and fail because the `hello-world` workload blocks internal traffic by default.

    ## Step 3: Enable Internal Access

    Update your `hello-world` workload to allow internal traffic. Add the `internal` block inside `firewallSpec`:

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        firewallSpec: {
          external: {
            inboundAllowCidrs: ["0.0.0.0/0"],
          },
          // Add internal firewall to allow traffic from same GVC
          internal: {
            inboundAllowType: "same-gvc",
          },
        },
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        firewall_spec=cpln.WorkloadFirewallSpecArgs(
            external=cpln.WorkloadFirewallSpecExternalArgs(
                inbound_allow_cidrs=["0.0.0.0/0"],
            ),
            # Add internal firewall to allow traffic from same GVC
            internal=cpln.WorkloadFirewallSpecInternalArgs(
                inbound_allow_type="same-gvc",
            ),
        )
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        FirewallSpec: &cpln.WorkloadFirewallSpecArgs{
        	External: &cpln.WorkloadFirewallSpecExternalArgs{
        		InboundAllowCidrs: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
        	},
        	// Add internal firewall to allow traffic from same GVC
        	Internal: &cpln.WorkloadFirewallSpecInternalArgs{
        		InboundAllowType: pulumi.String("same-gvc"),
        	},
        },
        ```
      </Tab>

      <Tab title="C#">
        ```csharp theme={null}
        FirewallSpec = new WorkloadFirewallSpecArgs
        {
            External = new WorkloadFirewallSpecExternalArgs
            {
                InboundAllowCidrs = new[] { "0.0.0.0/0" }
            },
            // Add internal firewall to allow traffic from same GVC
            Internal = new WorkloadFirewallSpecInternalArgs
            {
                InboundAllowType = "same-gvc"
            }
        }
        ```
      </Tab>
    </Tabs>

    Deploy the changes:

    ```bash theme={null}
    pulumi up
    ```

    ## Step 4: Verify Communication

    Wait 1-2 minutes for redeployment, then refresh the caller page. The `hello-world` workload now responds:

    ```text theme={null}
    Response from URL:

    ---
    Hello World!
    ---
    ```
  </Tab>
</Tabs>

## Internal Firewall Options

| Option             | Description                                 |
| ------------------ | ------------------------------------------- |
| **None** (default) | Block all internal traffic                  |
| **same-gvc**       | Allow from any workload in the same GVC     |
| **same-org**       | Allow from any workload in the organization |
| **workload-list**  | Allow from specific workloads only          |

<Note>
  The `workload-list` option requires `view` permission on the allowed workloads.
</Note>

## Internal Endpoint Format

Workloads communicate using internal DNS endpoints:

```text theme={null}
http://WORKLOAD.GVC.cpln.local:PORT
```

**Examples:**

* `http://api.production-gvc.cpln.local:8080`
* `http://cache.quickstart-gvc.cpln.local:6379`

<Note>The endpoint above load-balances across all replicas. For stateful workloads with `replicaDirect` enabled, you can also reach an individual replica directly — see [Finding Replica Hostnames](/reference/workload/general#finding-replica-hostnames).</Note>

## What You've Learned

* Workloads are **isolated by default** - internal traffic is blocked
* **mTLS is automatic** - no certificate configuration is required
* **Firewall rules** control which workloads are allowed to communicate
* Internal endpoints use the `.cpln.local` domain

## Clean Up

To delete all resources created through the quickstart series:

<Tabs>
  <Tab title="Console UI">
    1. Navigate to `GVCs`, select `quickstart-gvc`, and from `Actions` click `Delete`
    2. Navigate to `Domains`, select your domains, and from `Actions` click `Delete`
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    cpln gvc delete quickstart-gvc
    cpln domain delete app.example.com
    cpln domain delete example.com
    ```
  </Tab>

  <Tab title="Terraform">
    ```bash theme={null}
    terraform destroy
    ```
  </Tab>

  <Tab title="Pulumi">
    ```bash theme={null}
    pulumi destroy
    ```
  </Tab>
</Tabs>

<Note>
  Remember to remove the DNS records from your DNS provider after deleting the domains.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Push Your Own Image" icon="docker" href="/guides/push-image">
    Deploy your own containerized application on Control Plane.
  </Card>

  <Card title="Secrets" icon="key" href="/reference/secret">
    Manage sensitive configuration for your workloads.
  </Card>

  <Card title="Identities" icon="id-card" href="/reference/identity">
    Grant workloads access to cloud resources securely.
  </Card>

  <Card title="CI/CD Integration" icon="rotate" href="/guides/gitops">
    Automate deployments with your CI/CD pipeline.
  </Card>
</CardGroup>
