# Set up a Tunnel

<EnterpriseFeature name="Secure tunneling" />

This guide takes you from nothing to a working tunnel: you create the tunnel in
Zuplo, run the tunnel container inside your network, tell Zuplo which internal
services to expose, and confirm the connection is up.

For background on what a tunnel does and how it fits your architecture, see
[Secure tunnel](./secure-tunnel.mdx).

## Before you begin

You need:

- The [Zuplo CLI](../cli/overview.mdx) installed and authenticated.
- A Linux host or container runtime inside the network that holds your backend
  API. The host needs outbound internet access and internal DNS resolution to
  your backend.
- The internal endpoint of each service you want to expose, such as
  `http://payments.internal:8080`.

## Create the tunnel

<Stepper>

1. Create the tunnel in your Zuplo account:

   ```bash
   zuplo tunnel create --tunnel-name my-tunnel
   ```

   The command returns the tunnel ID, which starts with `tnl_`. Record it — the
   remaining commands need it. To look it up later, run `zuplo tunnel list`.

1. Retrieve the tunnel's connection token:

   ```bash
   zuplo tunnel describe --tunnel-id tnl_TRMZwunq2PLNQDwhu6A04Bmx
   ```

   Copy the token value from the output. The tunnel container authenticates with
   this token.

   :::caution

   The token grants the ability to connect to your tunnel. Store it in your
   platform's secret manager rather than in source control or a plain
   environment file. If a token is exposed, see
   [Rotate the tunnel token](./tunnel-advanced.mdx#rotate-the-tunnel-token).

   :::

</Stepper>

## Run the tunnel

Deploy the [`zuplo/tunnel`](https://hub.docker.com/r/zuplo/tunnel) container
anywhere inside your network. It takes a single environment variable,
`TUNNEL_TOKEN`, set to the token from the previous step.

To confirm the token works before you wire up your deployment platform, run the
container locally on a host inside your network:

```bash
docker run -d --name zuplo-tunnel \
  -e TUNNEL_TOKEN=<YOUR_TUNNEL_TOKEN> \
  zuplo/tunnel:latest
```

Replace `<YOUR_TUNNEL_TOKEN>` with the token you copied. For production, pin a
specific image tag instead of `latest` and inject the token from your secret
manager. For details, see
[Advanced tunnel configuration](./tunnel-advanced.mdx).

Run at least two instances so the tunnel survives the loss of a single host or
pod. Instances that share the same token act as replicas of one tunnel.

Where you deploy depends on your infrastructure. The following guides cover the
common platforms:

- [Deploy containers on AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/userguide/getting-started.html)
- [Deploy containers on Azure Container Instances](https://docs.microsoft.com/en-us/learn/modules/run-docker-with-azure-container-instances/)
- [Deploy container images on GCP](https://cloud.google.com/compute/docs/containers/deploying-containers)

For help with a platform that isn't listed, contact
[Zuplo support](mailto:support@zuplo.com).

## Expose your internal services

A running tunnel doesn't expose anything by itself. You define the services it
forwards to in a configuration file, then upload that file to Zuplo.

<Stepper>

1. Create a configuration file that names each service, its internal endpoint,
   and the Zuplo projects and environments allowed to call it:

   ```json title="tunnel-config.json"
   {
     "version": 1,
     "services": [
       {
         "name": "payments-api-prod",
         "endpoint": "http://payments.internal:8080",
         "configurations": [
           {
             "project": "my-project",
             "accessibleBy": ["production"]
           }
         ]
       },
       {
         "name": "payments-api-staging",
         "endpoint": "http://payments-staging.internal:8080",
         "configurations": [
           {
             "project": "my-project",
             "accessibleBy": ["preview", "working-copy"]
           }
         ]
       }
     ]
   }
   ```

   For the meaning of each property, see
   [Service configuration reference](./tunnel-services.mdx#service-configuration-reference).

1. Upload the configuration to your tunnel:

   ```bash
   zuplo tunnel services update \
     --tunnel-id tnl_TRMZwunq2PLNQDwhu6A04Bmx \
     --configuration-file ./tunnel-config.json
   ```

   The upload replaces the tunnel's entire service configuration, so include
   every service you want available each time you run it.

</Stepper>

## Verify the tunnel

<Stepper>

1. Check that the tunnel reports itself as connected:

   ```bash
   zuplo tunnel describe --tunnel-id tnl_TRMZwunq2PLNQDwhu6A04Bmx
   ```

   Look at the `status` field. A new tunnel takes a few seconds to register the
   first time it connects. If the status stays down, see
   [Troubleshoot a tunnel](./tunnel-troubleshooting.mdx).

1. Confirm Zuplo has the services you uploaded:

   ```bash
   zuplo tunnel services describe --tunnel-id tnl_TRMZwunq2PLNQDwhu6A04Bmx
   ```

1. Call a service from your gateway. Add a route to your project that rewrites
   to one of your service URLs, such as `service://payments-api-prod`, and send
   a request to it. For the full set of ways to call a service, see
   [Connect to tunnel services](./tunnel-services.mdx).

</Stepper>

## Next steps

- [Connect to tunnel services](./tunnel-services.mdx) — call `service://` URLs
  from handler code, route configuration, and environment variables.
- [Advanced tunnel configuration](./tunnel-advanced.mdx) — pin image versions,
  tune the connection, rotate tokens, and build your own image.
- [Troubleshoot a tunnel](./tunnel-troubleshooting.mdx) — work through a tunnel
  that's down or not passing traffic.
