Guides
CI/CD Preview Builds
Serve a pull request's build from your CI runner on a public HTTPS address, so reviewers open the running app without a staging server.
Summary
How it works#
1. The job builds the app and starts it on a local port of the runner.
2. The agent opens a tunnel from that port to the tunnel's public address.
3. The job posts the address on the pull request and stays alive while reviewers use it.
4. The job ends and the tunnel closes.
A tunnel has one address. Create one tunnel in the dashboard for previews and set its subdomain. In your CI settings, store the token as a secret named LOCALPORT_TOKEN and the address as a variable named PREVIEW_URL.
You need a Localport account and an HTTP tunnel created for this purpose.
GitHub Actions#
name: Preview
on:
pull_request:
types: [opened, synchronize]
concurrency:
group: preview
cancel-in-progress: true
jobs:
preview:
runs-on: ubuntu-latest
permissions:
pull-requests: write
env:
LOCALPORT_TOKEN: ${{ secrets.LOCALPORT_TOKEN }}
PREVIEW_URL: ${{ vars.PREVIEW_URL }}
steps:
- uses: actions/checkout@v4
- name: Build
run: |
npm ci
npm run build
- name: Start the app
run: npm run preview -- --port 3000 &
- name: Install the Localport agent
run: curl -fsSL https://localport.io/install.sh | sh
- name: Open the tunnel
run: localport http 3000 --region eu &
- name: Post the address
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Preview: ' + process.env.PREVIEW_URL
})
- name: Keep the preview up for 30 minutes
run: sleep 1800
The agent reads the token from LOCALPORT_TOKEN, so it never appears on a command line. With no terminal attached it prints plain log lines, one per event.
The concurrency block lets one preview run at a time. A new push cancels the run before it and takes the address over.
GitLab CI#
preview:
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- npm ci && npm run build
- npm run preview -- --port 3000 &
- curl -fsSL https://localport.io/install.sh | sh
- localport http 3000 --region eu &
- echo "Preview at $PREVIEW_URL"
- sleep 1800
Add LOCALPORT_TOKEN as a masked variable and PREVIEW_URL as a plain one in the project's CI/CD settings.
Several previews at once#
One tunnel serves one build at a time. When a second job connects with the same token, the newer one takes the address and the older preview stops.
To review several pull requests side by side, create one tunnel per slot and give each job its own token and address.
Limit who can open a preview#
A preview is public while the job runs. Add Basic Auth or an IP allowlist to the tunnel and only your reviewers can open it.
Frequently asked questions#
How long does a preview stay up?
For as long as the CI job runs. The examples hold the job for 30 minutes with sleep 1800. Change that number to suit your reviews, within your CI provider's job time limit.
Does every pull request get its own URL?
Each tunnel has one address. With one tunnel, the newest job holds it. Create several tunnels to keep several previews up at once.
How do I keep the token out of the logs?
Store it as a CI secret named LOCALPORT_TOKEN. The agent reads that variable, so the token is not passed as a flag and is not printed.
Does the agent need a terminal?
No. When its output is not a terminal the agent switches to plain log lines on its own. --noui forces the same mode.
Can a pipeline reach private devices instead of publishing a build?
Yes. That is Remote Access, and a job can authenticate with the token its CI platform mints, with no stored secret. See CI/CD with OIDC.
Is this a replacement for a staging environment?
It suits review and manual testing of one build. For load tests or an environment that stays up between jobs, deploy to a server.
What to read next#
- HTTP Tunnels. Create the tunnel and set its subdomain.
- Environment Variables. Every variable the agent reads.
- Basic Auth. A password in front of the preview.
- CI/CD with OIDC. Reach private devices from a pipeline.
- Installation. Install the agent on a runner.