Vocs
Hetzner.Website.Vocs deploys a Vocs
documentation project to a Hetzner Cloud
Server as a systemd
Service on port 3000. Vocs prerenders
static HTML; unmatched paths serve extensionless pages (/about →
about/index.html). The live URL is http://{ipv4}:3000 — there is
no TLS on the Service. Your vocs.config.* loads natively — there
is no adapter to install.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/frontend-frameworks; the resource loads
/vocs/node from your project at deploy time. It is only used at
build time, so a dev dependency is enough:
bun add -d @alchemy.run/frontend-frameworksnpm install -D @alchemy.run/frontend-frameworkspnpm add -D @alchemy.run/frontend-frameworksyarn add -D @alchemy.run/frontend-frameworksYour Vocs project already needs vocs installed.
Configure Vocs
Section titled “Configure Vocs”Your vocs.config.* file loads natively — title, sidebar, and any
other Vocs options work exactly as they do outside Alchemy:
import { defineConfig } from "vocs/config";
export default defineConfig({ title: "Docs", sidebar: [ { text: "Home", link: "/" }, { text: "Guide", link: "/guide" }, ],});If vocs.config.* sets outDir, pass the same value on the
resource (default "dist") so generated output stays outside the
rebuild hash:
export const Website = Hetzner.Website.Vocs("Website", { rootDir: "./docs", outDir: "build",});Declare the Website
Section titled “Declare the Website”Declare the site as a module-level const (rather than inline in the Stack):
import * as Hetzner from "alchemy/Hetzner";
export const Website = Hetzner.Website.Vocs("Website", { rootDir: "./docs",});Omit server to create a cpx12 / ubuntu-24.04 box in fsn1.
Pass an existing Hetzner.Server when several sites or APIs should
share one VM:
export const Box = Hetzner.Server("box", { serverType: "cpx12", image: "ubuntu-24.04", location: "fsn1",});
export const Website = Hetzner.Website.Vocs("Website", { rootDir: "./docs", server: Box,});Add it to the Stack
Section titled “Add it to the Stack”Yield the site from your Stack and return its URL:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyVocsSite", { providers: Hetzner.providers(), state: Alchemy.localState(), }, Effect.gen(function* () { const site = yield* Website; return { url: site.url }; }),);The live URL is http://{ipv4}:3000. The unit listens on port 3000
and answers GET /health (deployUnit curls that path whenever
PORT is set). site.server and site.service are set on deploy.
Add environment variables
Section titled “Add environment variables”Top-level env is copied onto process.env before build and
alchemy dev, and onto the systemd unit at deploy:
export const Website = Hetzner.Website.Vocs("Website", { rootDir: "./docs", env: { DOCS_TITLE: "Hello from Alchemy!", },});These are process environment variables, not Worker bindings.
Read the environment
Section titled “Read the environment”Vocs prerenders at build time, so page content that reads
process.env is baked into the HTML. The same values are set on
the hosted Node process; there is no request-time Vocs server.
const title = process.env.DOCS_TITLE ?? "Docs";Local development
Section titled “Local development”alchemy dev runs Vocs’ own dev server (native HMR) instead of
deploying; site.url is the local address and no Hetzner resources
are created (server and service are undefined). Wrap the site
in Alchemy.remote() to deploy the live Server and Service even
during dev:
export const Website = Hetzner.Website.Vocs("Website", { rootDir: "./docs",}).pipe(Alchemy.remote());Custom domain
Section titled “Custom domain”domain requires an existing Hetzner DNS Zone
(zone). Alchemy creates an A RecordSet
pointing at the Server’s public IPv4; url becomes
http://{domain}:3000. v1 does not provision a Zone — omitting
zone when domain is set fails:
export const Dns = Hetzner.Zone("dns", { name: "example.com",});
export const Website = Hetzner.Website.Vocs("Website", { rootDir: "./docs", domain: "docs.example.com", zone: Dns,});There is no TLS on the Service.
Where next
Section titled “Where next”Vocsreference — every prop and attribute- Servers and Services — the VM and systemd unit
- Zones & records — the Zone
domainrequires - Hetzner — the Hetzner provider hub