GoMark deploys as a static site. You build your markdown into plain HTML, CSS, JavaScript, and assets, then host the output on any static host. There is no long-running Go server to operate, and — because the in-browser Go runner executes client-side via WebAssembly — there is no execution backend to secure or scale either.
Build the site#
Use the gomark CLI to render your content directory into an output directory:
gomark build ./my_docs ./dist --url https://docs.example.com./my_docs— your content directory (markdown files)../dist— the output directory to create.--url— your public origin. Set it: it drives canonical URLs,sitemap.xml,
robots.txt, and Open Graph / Twitter image URLs. SEO metadata is wrong without it. (You can also set url: in gomark.yaml; the flag overrides it.)
Title, logo, SEO, navigation, and analytics come from an optional gomark.yaml that build auto-discovers. See the configuration guide.
The output is self-contained. It includes your rendered pages (<route>/index.html), copied assets, sitemap.xml, robots.txt, search-index.json for client-side search, and — when the runner is enabled — runner.wasm and wasm_exec.js.
Prefer Go over the CLI?
gomark.NewSite(...).Export("./dist")does the same thing, and so does setting theEXPORT_DIRenvironment variable.
Preview locally#
gomark serve ./my_docs --livegomark serve is a development tool, not a production server. With --live it renders pages on every request and auto-reloads your browser as files under the content directory change — including structural changes: adding, renaming, or deleting markdown updates routes, the sidebar, search, and the sitemap without a restart. Drop --live for a quick static-style preview. (You can't open the built files directly over file:// — the runner and search use fetch, which needs an HTTP origin — so use serve to preview.)
What you need from a host#
Any static host works. Two things make the experience seamless:
- **
.wasmserved asapplication/wasm.** Required for the fastest runner load path
(WebAssembly.instantiateStreaming). GoMark falls back to a slower load if the host sends the wrong type, so the runner still works either way — but the correct MIME type is preferred. Most modern hosts get this right automatically.
- Clean URLs. GoMark writes each page as
<route>/index.html, so hosts that serve
index.html for a directory (almost all of them) give you extensionless URLs with no configuration.
The 404 page#
gomark build always writes a themed 404.html to your output directory. GitHub Pages and Netlify pick it up automatically by filename, but other static hosts (Cloudflare Pages, Render, Surge, …) need to be told to route unknown paths there — without that, visitors hitting a bad link see a blank or generic error page instead of your themed one.
To cover those hosts, gomark build also writes a Netlify-style _redirects file alongside it:
/* /404.html 404This is additive: if your public-dir overlay already ships its own _redirects, GoMark leaves it alone so your rules win.
GitHub Pages#
Build in CI and publish the output. Save this as .github/workflows/pages.yml:
name: Deploy docs to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Build site
run: |
go install github.com/arivictor/gomark/cmd/gomark@latest
gomark build ./my_docs ./dist --url https://<user>.github.io/<repo>
- 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@v4Set --url to your Pages origin so canonical links and SEO metadata are correct. For a project site that origin includes the repo path (https://<user>.github.io/<repo>).
Netlify, Vercel, Cloudflare Pages#
Point the platform at your repo and configure:
- Build command:
go install github.com/arivictor/gomark/cmd/gomark@latest && gomark build ./my_docs ./dist --url https://your.site - Publish / output directory:
dist
These platforms handle application/wasm and clean URLs out of the box.
Amazon S3 + CloudFront#
gomark build ./my_docs ./dist --url https://docs.example.com
aws s3 sync ./dist s3://my-docs-bucket --deleteSet the bucket's static-website index document to index.html. Ensure objects ending in .wasm are served with Content-Type: application/wasm (set it on upload with aws s3 cp --content-type, or via a CloudFront response-headers policy), and invalidate the CloudFront distribution after each deploy.
Self-hosted (containers, nginx, Caddy)#
Serve the built output with any static web server. The two-stage container below installs the gomark CLI, renders your content directory, and serves the result with Caddy (which sends the correct application/wasm type automatically).
.
├── Dockerfile
├── gomark.yaml
├── Caddyfile
└── my_docs
├── index.md
└── ... other markdown files and assetsSave it as Dockerfile next to your content directory:
# Stage 1: install the gomark CLI and render your content to static files.
FROM golang:1.25-alpine AS builder
ENV CGO_ENABLED=0
RUN go install github.com/arivictor/gomark/cmd/gomark@latest
WORKDIR /src
COPY . .
RUN gomark build ./my_docs /out/site --url https://docs.example.com
# or use gomark.yaml
# Stage 2: serve the static output with Caddy.
FROM caddy:2-alpine AS site
COPY Caddyfile /etc/caddy/Caddyfile
COPY --from=builder /out/site /usr/share/caddy
EXPOSE 80command-line values will override
gomark.yaml, so you can set--urlhere for correct canonical links and SEO.
Caddy needs a config to point at the rendered files. Save this as Caddyfile alongside the Dockerfile (the .wasm header is optional — Caddy already serves application/wasm correctly, but it makes the intent explicit):
:80 {
root * /usr/share/caddy
file_server
encode gzip zstd
@wasm path *.wasm
header @wasm Content-Type application/wasm
}docker build -t my-docs .
docker run -p 8080:80 my-docsThen browse to http://localhost:8080. Adjust ./my_docs and --url to match your content directory and public origin.
With nginx, copy dist into the web root and confirm application/wasm is in mime.types (it is on current nginx builds); add try_files $uri $uri/ $uri/index.html =404; for clean URLs.
See our sites repo for a real-world example.
Updating the runner#
The committed public/runner.wasm.gz is the prebuilt in-browser runner and is embedded in the CLI, so gomark build ships it automatically. If you change the runner source under cmd/wasm, regenerate it with scripts/build-wasm.sh before building.
Deployment checklist#
- Build with
gomark build <content> <output>(or define values ingomark.yaml). - Pass
--url(your public origin) for correct canonical links and SEO. - Upload the output directory to your static host.
- Confirm
.wasmis served asapplication/wasm(most hosts do this automatically). - Pass
--no-runner(or setbuild.runner: falseingomark.yaml) if you don't want the in-browser runner.