# https://tunnels.io/use-cases/ci-cd

[Home](https://tunnels.io/) / [Use cases](https://tunnels.io/use-cases) / CI and CD

Use case: continuous integration

# A public URL for every pull request.

Your build has a URL problem. The service you need to test against cannot reach a container inside a CI runner, and neither can the reviewer waiting on your pull request. A tunnel gives that runner a public HTTPS address for as long as the job is alive, and takes it away afterwards.

[Get started free →](https://tunnels.io/get-started) [Read the docs](https://tunnels.io/docs)

## Two jobs, and they are not the same job

Decide which one you are doing first. They look identical in the log and they want opposite things from a hostname.

### Inbound: a machine calls the build

A webhook provider firing a test event, a third-party sandbox posting a callback, a partner API finishing an async flow. Nobody reads this URL. The caller usually needs the address registered in advance, so it wants a fixed name every build reuses.

### Preview: a person looks at it

A reviewer opening the branch on a phone, a designer checking a layout, someone signing off before merge. The name should say which pull request it is, and it has to stay up while the reviewer looks.

The inbound job wants a small pool of names reused by every build. The preview job wants one per pull request, released at merge.

## How does the client get onto a CI runner?

One shell line, or a binary already in the image. Published builds cover eleven targets including arm64 and armv7, so an ARM runner is no special case. The snippets live in [the CI configuration reference](https://tunnels.io/docs).

| Runner | Installing the client | Dedicated CI token | Concurrency to watch |
| --- | --- | --- | --- |
| GitHub Actions | Install script in a run step; runners are ephemeral. | Yes. One repository or organisation secret. | Matrix legs and parallel pull requests each open a tunnel. Group them. |
| GitLab CI | In `before_script`, or baked into the job image. | Yes. A masked variable, protected if only protected branches need it. | Parallel jobs across runners. A resource group serialises those sharing a name. |
| CircleCI | A run step, or a custom image with the binary. | Yes. Held in a context, so projects share one token. | Parallelism splits a job across containers. Keep the tunnel in one. |
| Jenkins | Once on a long-lived agent, or per build. | Yes, and it matters most here. Credentials store, never the Jenkinsfile. | Executors will start the same job twice. Disable concurrent builds. |
| Buildkite | An agent hook, or baked into the agent image. | Yes. Agent environment or a secrets hook, never the pipeline file. | Agents scale out horizontally. A concurrency group on the step caps holders. |

## Which token should a build use?

Not the one on your laptop. Issue a token that only CI uses and store it as a secret in the runner. The point is revocation: when a log leaks, you kill that token and your own machine keeps working.

```
# from the runner's secret store, never from the repo
$ tunnels config add-authtoken "$TUNNELS_TOKEN"
$ tunnels --subdomain ci-callback --no-color http 8080
Active Tunnels:
▸ https://ci-callback.tunnels.host → localhost:8080  [https]
```

Mask the value so a debug step cannot echo it, and let it land in the config file on the runner, which an ephemeral container throws away with itself. Label it, because an unlabelled token is one nobody dares revoke. If it must outlive whoever created it, use an organisation-scoped workspace on Team at $29 a month.

## What should the subdomain be called?

Short, lowercase, and built from a number that already exists. The pull request number is unique, stable, and already in an environment variable. A name like `pr-482` tells a reviewer what they are looking at. Branch names carry slashes, run long, and get renamed mid-review.

The part teams forget is the release. A reserved name is an allowance, not an infinite namespace, and a name nobody gives back eats that allowance. Professional includes three reserved subdomains, Gold ten static names plus wildcard, Premium uncapped. Three is a rotating pool: recycle `ci-1` to `ci-3` or buy the plan that matches your open-PR count.

On the free plan you do not choose at all. It issues a random sixteen-character subdomain, reassigned on restart: fine for a callback, useless in a review. More in [why a stable name behaves differently](https://tunnels.io/learn/what-is-a-reserved-subdomain).

## How many tunnels can run at once?

As many as your plan allows, and CI finds the ceiling. One tunnel on Basic and Student, five on Professional, ten on Gold, unlimited on Premium. Say the first number plainly: on the free plan, your build and your laptop cannot both be connected.

Then count what a pipeline opens. Three open pull requests, each running a matrix of two operating systems, is six tunnels. Five covers a small team with a serialised pipeline, ten a busy repository, unlimited a preview per pull request across several repositories.

Before upgrading, try a concurrency group. Every runner in the table has one. Cap the pipeline at one tunnel-holding job per branch.

## Which plan you need

Professional at $4 a month, or $40 a year, is the honest starting point. It is the first plan with reserved subdomains, which is the whole reason a preview URL is worth anything, and five tunnels stop the build and your machine fighting. It is also the first plan with raw TCP.

Gold at $10 a month, or $96 a year, adds ten tunnels, ten static names, wildcard and custom domains. Premium at $20 a month, or $192 a year, removes the tunnel cap and the lease expiry. A 24-month prepay exists on all three: $72, $144 and $288.

Bandwidth is rarely what bites: Professional includes 128 GB a month, Gold 256 GB, Premium 512 GB, and a smoke test moves very little. Budget for tunnel count and reserved names, not traffic.

[Compare plans →](https://tunnels.io/pricing)

## What happens when the job is cancelled?

The tunnel is a process, so it dies with the container whether the job passed, failed, or was cancelled. What does not clean itself up is whatever you registered elsewhere.

Put the teardown in a step that runs on cancellation, not only on success. Every runner has the idea: an always-condition, an after-script, a post step, a pre-exit hook. It removes the endpoint you registered at a third party and releases the name. Skip it and a webhook keeps pointing at a hostname nothing is serving, which surfaces days later as missing events.

A wobbly runner network is one thing you need not engineer around: the client reconnects on exponential backoff with jitter and honours a server-directed delay. Set a job timeout anyway when a preview waits on a human.

## What this does not do

This is not a preview-deployment platform. The URL points at a machine that stops when the job stops.

Nothing survives the job

No build cache, no artefact hosting, no queueing. When the runner exits, the preview is gone. A link opened tomorrow needs your runner still running.

Basic auth, and that is the list

You can put HTTP basic auth in front of a tunnel with `--httpauth user:pass`, enforced at the edge. There is no OAuth, no IP allowlisting, no request replay.

HTTPS and raw TCP only

UDP is not supported. There is no SMTP tunnel type either, though SMTP is itself TCP and so rides a raw TCP tunnel like any other TCP service, so a pipeline testing a mail sender needs another tool. TLS is terminated at the edge, TLS 1.2 minimum.

## Questions

### Can I give every pull request its own preview URL?

Yes on a paid plan, and the constraint is how many names you may hold. Professional includes three reserved subdomains, Gold ten static names plus wildcard and custom domains, Premium uncapped. The free plan issues a random sixteen-character subdomain that changes on restart, so it cannot give a predictable per-PR name.

### Which token should I put in my CI secrets?

A dedicated one, used nowhere else. Store it in the runner's secret store, mask it, and apply it at the start of the job. The reason is revocation: a CI-only token can be killed the moment a log leaks, without breaking your laptop.

### What happens to the URL when the build finishes?

The client exits with the job, the tunnel closes, and the URL stops serving. A reserved subdomain stays yours to reuse, but nothing is hosted once the runner is gone, so a reviewer who opens the link an hour later gets nothing.

### Do I need a paid plan to run a tunnel in CI?

Not to prove it works. The free Basic plan gives one tunnel with a random URL, enough for a job that only needs something to call it. It stops being enough when you want a name a reviewer can read, or CI and your laptop connected at once: Professional, $4 a month.

### Can a build expose a database or another non-HTTP port?

Yes, over raw TCP, which starts on Professional at $4 a month. The free plan cannot open a TCP tunnel at all. HTTPS and raw TCP are the only protocols we carry, so UDP and SMTP are out on every plan.

## Read next

[Webhooks The same tunnel pointed at your own machine.](https://tunnels.io/use-cases/webhooks) [Mobile testing Getting a real device onto a build elsewhere.](https://tunnels.io/use-cases/mobile-testing) [tunnels.io vs ngrok Flat plans against metered endpoint hours.](https://tunnels.io/compare/ngrok)

Also useful: [webhook delivery in detail](https://tunnels.io/learn/how-to-test-webhooks-locally) and [the full client reference](https://tunnels.io/docs).

Put a URL on your next build.

Start free, then upgrade when you want a name a reviewer can read.

[Get started →](https://tunnels.io/get-started) [See pricing](https://tunnels.io/pricing)
