Steno

Deploying

steno build writes plain files into dist/ (or whatever you set output to): HTML, CSS, JavaScript, images. There is no server, no database, and nothing that needs to keep running. That means you can put it on almost any host built for static files. This page walks through the common ones.

Every option below runs the same two commands, they just differ in where those commands run and where the result ends up:

deno install # only needed if the project has dependencies to fetch first
deno task build

If your project doesn't have a deno task build (for example, you didn't scaffold it with deno create jsr:@steno/init), use deno run --allow-read --allow-write --allow-net --allow-env jsr:@steno/steno build instead.

GitHub Actions

This workflow builds your site on every push to main and publishes it to GitHub Pages. Save it as .github/workflows/deploy.yml:

name: Deploy

on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: denoland/setup-deno@v2
        with:
          deno-version: v2.x
      - run: deno task build
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

Then turn on GitHub Pages for the repository (Settings, then Pages, then set Source to "GitHub Actions"). If your project doesn't have a deno task build, replace that step's run: line with the deno run ... command above.

You don't have to deploy to GitHub Pages specifically, this same build-then-upload-pages-artifact shape works with almost any GitHub Actions deploy step; swap the last job for whatever your host's official action is (see Vercel, Netlify, and Cloudflare Pages below, most of them publish one).

Vercel, Netlify, and Cloudflare Pages

These three all work the same way: point them at your repository, tell them how to build your site and where the result lands, and they take care of the rest, including a new deployment on every push.

Whichever one you use, the settings are the same:

  • Build command: deno task build (or the deno run ... command above if your project doesn't define that task)
  • Output directory: dist
  • Framework preset: pick "Other" (or "None") since none of them know Steno specifically

The one thing to check is that Deno itself is available during the build. Some hosts have it preinstalled; if yours doesn't, add an install step before the build command:

curl -fsSL https://deno.land/install.sh | sh -s -- --yes
export PATH="$HOME/.deno/bin:$PATH"
deno task build

If you're on Cloudflare Pages (or any CDN in front of your site) and worried about stale cached CSS or JavaScript after a redeploy, you don't need to do anything extra: Steno already gives theme CSS and JS files a content hash in their filename by default, so a changed file gets a brand new URL automatically. See hashAssets in Configuration if you ever want to turn that off.

Self-hosting (a server or a Docker container)

If you're running your own server, a VPS, or a platform like Dokploy or Coolify that expects a long-running container, build the site the same way and then serve the dist/ folder with any static file server. Don't use steno preview for this: it only listens on 127.0.0.1 (your own machine), by design, so it is meant for a final local check before you ship, not for serving real traffic.

A minimal Dockerfile that builds the site and serves it with Caddy:

FROM denoland/deno:2.9.4 AS build
WORKDIR /app
COPY . .
RUN deno task build

FROM caddy:2-alpine
COPY --from=build /app/dist /usr/share/caddy

Any other static file server works just as well, nginx, serve, Caddy, or your platform's built-in one, as long as it binds to 0.0.0.0 (all interfaces) rather than 127.0.0.1, so traffic from outside the container can actually reach it.

What to read next

  • Configuration for output and other build-time settings.
  • Transactional builds for what happens if a build fails partway through, useful context if you're scripting deploys yourself.

Type to search guides, configuration, Tau, plugins, and API references.