GOWDK currently supports three practical output shapes:
- Build output files from
gowdk build --out. - A generated Go app from
gowdk build --out --app. - A local-platform binary or Go
js/wasmartifact from the generated app.
Deployment orchestration is user-owned. GOWDK does not generate containers, Kubernetes manifests, platform adapters, or CDN configuration.
Build build output:
gowdk build --out dist/siteDeploy the contents of dist/site with any asset host that can serve
directory indexes:
dist/site/
index.html
routes...
assets...
gowdk-routes.json
gowdk-assets.json
gowdk-build-report.json
Local smoke test:
gowdk serve --dir dist/site --addr 127.0.0.1:8080gowdk serve serves generated build output from disk. It does not run generated
request-time features.
Build build output, generated app source, and a local binary:
gowdk build --out dist/site --app .gowdk/app --bin bin/siteRun the binary:
./bin/siteThe generated app embeds the selected build output and serves it through
runtime/app. It also exposes:
/_gowdk/healthX-GOWDK-*identity response headers
Generated apps may attach runtime/app.Metrics to the runtime handler. When
present, /_gowdk/health includes a snapshot of request, static, backend,
action, API, SSR, not-found, method-not-allowed, and CSRF-unavailable counters.
Runtime identity environment variables:
GOWDK_APP_ID: application identity metadata.GOWDK_MODULE_NAME: module identity metadata.GOWDK_INSTANCE_ID: stable runtime instance ID. If omitted, one is generated at process start.
The selected module set is fixed at build time. GOWDK_MODULE_NAME does not
change which files were embedded.
Single-binary deploy is the primary GOWDK differentiator. Prefer this path when the app needs generated actions, APIs, partial fragments, guards, CSRF, SSR, or embedded assets in one artifact.
GOWDK does not generate Dockerfiles. A minimal container can copy a compiled binary:
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY bin/site /app/site
ENV GOWDK_ADDR=0.0.0.0:8080
EXPOSE 8080
ENTRYPOINT ["/app/site"]Build the binary before the image:
gowdk build --out dist/site --app .gowdk/app --bin bin/site
docker build -t my-gowdk-site .
docker run --rm -p 8080:8080 my-gowdk-sitePass app secrets, CSRF secrets, database URLs, and service credentials as runtime environment variables owned by your deployment platform.
Generated CSRF currently validates tokens with one active signing secret from
Build.CSRF.SecretEnv or GOWDK_CSRF_SECRET. There is no multi-key grace
period yet.
Rotate CSRF secrets as a coordinated deploy:
- Build and smoke-test the new binary.
- Set the new secret in the deployment platform.
- Restart or replace every generated app instance that serves action POSTs.
- Confirm
/_gowdk/healthis reachable on every instance. - Expect forms rendered before the rotation to fail with HTTP 403
invalid csrf token; users should reload the page and resubmit.
Do not run mixed old/new CSRF secrets behind the same load balancer for longer than the deploy window. If a rollback is needed, restore both the previous binary and the previous CSRF secret.
Run the single binary under systemd when deploying to a Linux VM:
[Unit]
Description=GOWDK site
After=network.target
[Service]
WorkingDirectory=/opt/gowdk-site
ExecStart=/opt/gowdk-site/bin/site
Environment=GOWDK_ADDR=127.0.0.1:8080
Environment=GOWDK_APP_ID=site
Restart=on-failure
RestartSec=2s
User=gowdk
Group=gowdk
[Install]
WantedBy=multi-user.targetKeep secrets in systemd drop-ins, an environment file with correct filesystem permissions, or the host secret manager. Do not commit them to the repository.
Generated binaries speak plain HTTP. Put TLS, HTTP/2, compression, and public host routing in a normal reverse proxy.
Caddy:
example.com {
reverse_proxy 127.0.0.1:8080
}nginx:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Configure trusted proxy behavior in app-owned middleware when handlers depend on forwarded IP, host, or scheme values.
Generated binaries use explicit cache headers:
- Embedded SPA HTML uses
Cache-Control: no-cacheby default, so browsers may store it but must revalidate before reuse. A page-level@cacheoverrides this default for successful static SPA HTML generated by that page. - Generated CSS and generated browser runtime assets recorded in
gowdk-assets.jsonuse their recorded cache policy. The current generated policy isCache-Control: public, max-age=31536000, immutablewith SHA-256 content hashes in the asset manifest. Generated CSS is minified and emitted with a content-hashed filename; the asset manifest maps the stable logical CSS path to the emitted hashed path. - CSRF-personalized HTML, action responses, API responses, partial fragments,
SSR HTML without an explicit
@cache, SSR load redirects, generated handler errors, generated error pages, and invalid-CSRF responses useCache-Control: no-store. - Page-level
@cacherecords route response cache intent in compiler, route, manifest, generated asset metadata, and generated SSR route metadata. Generated binaries apply it to successful static SPA HTML and SSR HTML responses for that page. It does not override the no-store safety policy for actions, APIs, partial responses, load redirects, generated errors, or CSRF-mutated HTML. - Page-level
@revalidaterequires@cacheand appendsstale-while-revalidate=<seconds>to the generated Cache-Control header for successful static SPA HTML and SSR HTML responses. Accepted values are whole seconds or whole-second durations such as60s,5m, or1h.
For pure build-time output, deploy gowdk build --out dist/site to any static
host that supports directory indexes.
Recommended CDN policy:
- Respect generated
Cache-Controlheaders when serving through a generated binary. - For static-file hosting, cache content-hashed assets under
assets/for a long time. - Revalidate HTML unless the page has an explicit
@cachepolicy. - Do not cache action/API/fragment/SSR error responses from a generated binary.
Cloudflare Pages, Vercel, and Netlify can serve static dist/site output when
the app does not need generated request-time handlers. Use a generated binary,
container, VM, or platform that can run Go when the app needs actions, APIs,
fragments, SSR, guards, CSRF, or server validation.
Cloudflare Workers compatibility is limited to generated static output or the
separate Go js/wasm deploy artifact. GOWDK does not currently emit a Workers
adapter.
Kubernetes guidance is intentionally not generated. Use normal container and service manifests around the Docker/single-binary shape only when your deployment environment already requires Kubernetes.
Keep each release artifact immutable:
- the generated binary;
- the generated build output used to create that binary;
- the config and environment variable set used at runtime;
- the checksum and attestation for the artifact.
Rollback means restoring the previous known-good artifact and its matching
runtime configuration. For single-binary deploys, keep the previous binary on
the host or in the image registry and switch the process manager, container tag,
or deployment descriptor back to that version. For static hosts, redeploy the
previous dist/site output directory. For split frontend/backend deploys,
rollback both sides together when route, endpoint, CSRF, or asset manifests
changed.
After rollback, verify:
curl -fsS http://127.0.0.1:8080/_gowdk/healthThen smoke-test one static page and one generated request-time route if the app uses actions, APIs, fragments, SSR, guards, or CSRF.
Use modules for source selection:
gowdk build --module public --out dist/public --app .gowdk/public --bin bin/public
gowdk build --module admin,api --out dist/admin-api --app .gowdk/admin-api --bin bin/admin-apiUse Build.Targets for repeatable packaging:
Build: gowdk.BuildConfig{
Targets: []gowdk.BuildTargetConfig{
{
Name: "public",
Modules: []string{"public"},
Output: "dist/public",
App: ".gowdk/public",
Binary: "bin/public",
},
{
Name: "admin",
Modules: []string{"admin"},
Output: "dist/admin",
App: ".gowdk/admin",
Binary: "bin/admin",
},
},
}Run every target:
gowdk buildRun one target:
gowdk build --target adminUse distinct Output and App directories for separate binaries.
--wasm compiles the generated app with GOOS=js GOARCH=wasm:
gowdk build --out dist/site --app .gowdk/app --wasm bin/site.wasmThis is a Go js/wasm deploy artifact for runtimes that can execute that
artifact. It is separate from browser island assets emitted for component-level
@wasm declarations.
Addons are normal Go packages imported by gowdk.config.go:
import (
"github.com/cssbruno/gowdk/addons/actions"
"github.com/cssbruno/gowdk/addons/partial"
)
var Config = gowdk.Config{
Addons: []gowdk.Addon{
actions.Addon(),
partial.Addon(),
},
}Third-party addons should ship as Go modules. Versioning follows Go module versions, not CLI plugin discovery. GOWDK should not load production addons from runtime filesystem scans, network registries, or hidden project metadata.
Generated binaries currently support:
- Embedded app file serving.
- Feature-bound same-package action handlers with no-input, typed value, typed
pointer, or
form.Valuessignatures. - Feature-bound same-package API handlers.
- No-store panic boundaries for generated SSR, action, and API request-time lanes.
- First-slice same-page POST action redirects.
- CSRF-wired generated action handlers when
Build.CSRF.Enabledis set and the configured secret environment variable is present. - First-slice required-field validation for directly declared form controls.
- First-slice partial action fragment responses.
- First-slice concrete and dynamic request-time SSR pages with declared
load {}identifier or dotted paths. - Optional split frontend/backend generation with
--backend-appand--backend-bin; the frontend proxies backend routes toGOWDK_BACKEND_ORIGIN.
Generated binaries do not yet support:
- Hybrid streaming, data refresh, and non-HTTP revalidation.
dev rebuilds generated build output, serves it locally, and live reloads the
browser after successful rebuilds:
gowdk dev --out dist/site
gowdk dev --target admin