Skip to main content

Preview Environments per Pull Request, for Less Than a Cent an Hour

· 7 min read

Short answer: a preview environment on Hostim is just one more app per pull request. A GitHub workflow deploys pr-42-site and pr-42-api when the PR opens, redeploys them on every push, comments the links on the PR, and deletes both when the PR is closed or merged. On the smallest plan an app costs about €0.0034 an hour, so a two-app preview that lives for two days is around 33 cents.

This came up while I was writing our static site hosting guide. One of the limits I listed was "no deploy previews", and then I sat there looking at it – we bill by the hour, an app takes 30 seconds to create, and deleting it is one command. The feature was already there, it just had no name.


What you get​

  • Every pull request gets its own apps, with their own URLs, built from that PR's code.
  • Every push to the PR redeploys them. The URLs stay the same, so the PR comment stays valid.
  • The workflow posts one comment with the links and edits it on every push instead of spamming a new one.
  • Closing or merging the PR deletes the apps. Billing stops with them.

It is not only for frontends. The example below deploys a static site and a small Node API side by side, because that is where the Netlify-style previews stop – they preview your frontend, and your backend is someone else's problem.


The workflow​

You need three things: a Hostim API token, a project for previews, and this file.

  1. Create an API token in the console and add it to the repository as the secret HOSTIM_TOKEN (Settings → Secrets and variables → Actions).
  2. Create a project for the previews, once: hostim projects create previews --region eu-center.
  3. Add .github/workflows/preview.yml:
name: preview

on:
pull_request:
types: [opened, synchronize, reopened, closed]

# One run per pull request at a time; a new push cancels the old deploy.
concurrency:
group: preview-${{ github.event.number }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: write

env:
HOSTIM_TOKEN: ${{ secrets.HOSTIM_TOKEN }}
HOSTIM_PROJECT: previews
PR: pr-${{ github.event.number }}

jobs:
deploy:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install the Hostim CLI
run: curl -fsSL https://raw.githubusercontent.com/hostimdev/cli/main/install.sh | sh
- name: Deploy the preview
id: deploy
run: |
api=$(hostim deploy "$PR-api" --source api --port 3000 --plan sa-1-1 \
--env GIT_SHA=${{ github.event.pull_request.head.sha }} -o json | jq -r .url)
site=$(hostim deploy "$PR-site" --source site --plan sa-1-1 -o json | jq -r .url)
echo "api=$api" >> "$GITHUB_OUTPUT"
echo "site=$site" >> "$GITHUB_OUTPUT"
- name: Post the links on the pull request
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} \
--edit-last --create-if-none --body "Preview for ${{ github.event.pull_request.head.sha }}:
- site: ${{ steps.deploy.outputs.site }}
- api: ${{ steps.deploy.outputs.api }}"

cleanup:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Install the Hostim CLI
run: curl -fsSL https://raw.githubusercontent.com/hostimdev/cli/main/install.sh | sh
- name: Delete the preview apps
# A deploy that failed early may not have created both apps.
run: |
for app in "$PR-site" "$PR-api"; do
hostim apps get "$app" >/dev/null 2>&1 && hostim apps rm "$app" --yes
done

What the moving parts do:

  • hostim deploy <app> --source <dir> uploads the folder and builds it. If the app does not exist yet, it creates it; if it does, it updates it. That is why the same step covers both "PR opened" and "new push".
  • site/ has an index.html and no Dockerfile, so the CLI serves it with nginx on port 80 – no --port, no Dockerfile. For an Astro, Hugo or Next.js site, add your build step before the deploy and point --source at dist, public or out.
  • api/ has a Dockerfile, so it is built like any other app. Any language works.
  • -o json | jq -r .url gives you the app's URL, so the comment has real links.
  • concurrency with cancel-in-progress stops two pushes in quick succession from racing each other.

In my test repo the whole deploy job – checkout, CLI install, both apps – took 58 seconds when the PR opened and 50 seconds after a push. The links were up when the comment appeared, the second push edited that comment instead of adding one, and the cleanup job deleted both apps in 7 seconds after I closed the PR.


What it costs​

The smallest app plan, sa-1-1 (1 vCPU, 1 GB RAM), is €2.50 a month. Billing is hourly with a one-hour minimum, so:

Preview lives1 app2 apps (site + API)
1 hour€0.0034€0.0068
1 day€0.08€0.16
2 days€0.16€0.33
1 week€0.58€1.15

Traffic is included. There is no per-build or per-deploy charge, so pushing twenty times to the same PR costs the same as pushing once. A team that opens 30 PRs a month and merges each within two days pays around €10 for all of their previews.


What it does not do​

I'd rather you hear these from me than find them yourself:

  • No copy of your database. A preview app gets whatever env you give it. The cheap option is pointing previews at a shared staging database. A database per PR (hostim db can create one, and the cleanup job can delete it) costs extra and means you need migrations and seed data that run on an empty database – which is good hygiene, but it is work.
  • Pull requests from forks get no secrets. GitHub does not pass HOSTIM_TOKEN to workflows triggered by a fork's PR, so those runs fail at deploy. That is a GitHub safety rule, and for previews of public repos you want it. Run previews for branches in your own repo.
  • No CDN. A preview runs in Germany like the rest of your apps. For a preview that is fine.
  • You own the workflow. It is a YAML file in your repo, not a checkbox. If the cleanup job fails (token expired, GitHub outage), the apps stay up and keep billing until you delete them. hostim apps ls -p previews shows what is left.

FAQ​

Does Hostim have deploy previews? Not as a button. Each pull request becomes its own apps through a GitHub workflow: deploy on open and on every push, delete on close. The full workflow is above.

How much does a preview environment cost? About €0.0034 per app per hour on the smallest plan, billed hourly. A two-app preview that lives two days is around 33 cents. Traffic is included and there is no charge per deploy.

Does it work for backends, not only static sites? Yes. Anything with a Dockerfile deploys the same way with --port. The example deploys a static site and a Node API side by side.

Can I use the Hostim GitHub Action instead? The Hostim GitHub Action rebuilds or redeploys an app that already exists. Previews need apps created and deleted per PR, so this workflow calls the CLI directly.

What happens if I forget to clean up? The cleanup job runs on every close and merge. If it fails, the apps keep running and billing. hostim apps ls -p previews lists them and hostim apps rm <app> --yes deletes one.


If you want previews of the whole thing – frontend, API, worker – and not only the part that fits on a CDN, that is easy on Hostim: EU-hosted, flat monthly price, managed Postgres, MySQL and Redis.

👉 Deploy an app on Hostim, free 5-day trial, no card

We're building Hostim.dev to make this simpler, and we are happy to answer any questions.

Last updated and tested: 28 October 2026, with hostim CLI v0.1.54.