pw build
pw build [--debug] [--backend nethttp|fasthttp] [--target lambda|azure-functions|google-cloud-run-functions|vercel-go]pw build turns the current project state into a release artifact. With no
target it produces the ordinary binary. --backend selects the HTTP
implementation and defaults to nethttp; --target selects provider
packaging.
What it does
Section titled “What it does”- compiles templates, SQL, page trees, catalogs, and binding call sites into
_pw_gen.gofiles beside their sources; - builds the Tailwind stylesheet minified, if Tailwind is enabled — this
overrides
assets.tailwind.minify, so a release is never accidentally unminified; - builds the asset tree into
dist/public: it converts what the project asked to convert, writes the*.br,*.zstdand*.gzsidecars, and emits the manifest that decides every cache header; - rejects the build if
project.maindepends on a development-only package; - runs
go buildonproject.mainfrompopcornweb.toml.
Steps 1 through 4 are exactly pw generate. This
command is defined as that one plus the compiler, so the two cannot drift apart
in content or in order.
The binary lands in the project root, named after the main package. The
scaffolded .gitignore already excludes it, along with everything under
dist/ — the built tree, the conversion cache, and the manifest are all build
output.
With --target, the result instead lands under
.pw/build/<target>/<backend>/. Lambda and Azure Functions receive a Linux
binary plus provider metadata; Google Cloud Run functions and Vercel Go receive
a locally compiled, vendored source tree. See Serverless Hosting
for each artifact contract.
Today, contrib/devidp is the only development-only package. It is the identity
provider used by pw dev, and it signs users in without
checking a password. Linking that behavior into a deployable binary is a build
defect, not a production setting, so pw build stops and names the importing
package.
Debug artifacts
Section titled “Debug artifacts”pw build --debug--debug keeps the debug information a deployable artifact otherwise drops: the
source map the script build emits, and the DWARF and symbol table that
-ldflags=-s -w removes. Nothing else about the build changes.
Reach for it when a shared test or CD deployment is being debugged by more than one person. Do not reach for it for staging, which exists to rehearse production — an artifact that differs from the production one rehearses nothing.
Without it the map is absent, and the bundle carries no sourceMappingURL
comment either, because a bundle naming a map the tree does not hold turns every
devtools open into a request for a file that is not there. The two shapes give
the bundle the same hashed name, so the URL a page loads does not depend on which
one produced it. Panic stacks still carry function names and line numbers in both:
pw build retains Go’s pclntab either way.
--debug brings back nothing from pw dev. The error
overlay, the launcher, and the development identity provider are absent from a
pw build artifact of either shape, and step 4 above still refuses a build that
imports one.
Running the result
Section titled “Running the result”APP_ENV=prod ./myappAPP_ENV selects which project-local configuration file is read; see
Application Configuration.
Cross-compiling and TinyGo
Section titled “Cross-compiling and TinyGo”pw build shells out to go build, so the usual environment variables apply:
GOOS=linux GOARCH=amd64 pw buildThe generated path uses no runtime reflection, so the same sources can target
TinyGo. pw build always links with host go, so a TinyGo build generates and
then invokes that compiler itself:
pw generatetinygo build -scheduler=threads -o myapp ./cmd/myapppw generate is this command without its final step —
it leaves the whole tree a compiler needs, including the dist/public that
public.go names in a go:embed directive. Do not narrow it with
--code-only here; that flag writes the generated Go alone, and the compiler
then fails on a directory nothing built.
-scheduler=threads is required for any engine that speaks a network protocol.
Under the cooperative scheduler a blocking socket call holds the whole runtime,
so a driver’s cancellation watcher never runs and a query outlives its context
deadline without reporting one. The database/postgres and database/mysql
packages refuse to compile without the flag rather than letting that happen at
run time.
Container Images uses both commands in
the two Dockerfiles pw init writes.
TinyGo’s net package has no networking implementation of its own; every socket
passes through a Netdever registered by the program. Projects scaffolded with
TinyGo support include a root tinygohelper.go for that registration:
//go:build tinygo
package publicassets
import _ "github.com/shibukawa/tinygodriver/netdev"The //go:build tinygo constraint keeps the file out of host Go builds. Without
it a TinyGo binary compiles fine and then exits at startup:
2026/01/01 00:00:00 Netdev not setOnly a project created with --tinygo, or with that wizard answer, gets the
file — it is not the default. Add it by hand before switching a project to
TinyGo.
Verify that generated code is current before building:
pw checkpw build