Deployment
Gastro applications compile to a single binary with embedded templates and static assets. Deploy by copying one file.
Building for Production
Generate the Go code and cross-compile for your target platform:
# Generate Go code from .gastro files
gastro generate
# Cross-compile for Linux
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o dist/myapp .
# Deploy the single binary
scp dist/myapp server:/opt/myapp
Or use the shorthand:
# Or use gastro build for generate + compile
gastro build
./app
The resulting binary contains everything: your Go handlers, compiled templates, and static assets from static/. No runtime dependencies.
Docker
A multi-stage Dockerfile keeps the image small. The build stage compiles everything, and the runtime stage contains only the binary:
FROM golang:1.27-alpine AS build
WORKDIR /src
# Install the gastro CLI
COPY . /gastro-src
RUN cd /gastro-src && go build -o /usr/local/bin/gastro ./cmd/gastro/
# Copy project files
COPY examples/gastro/ .
# Generate and build
RUN gastro generate
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app .
FROM alpine:3
RUN adduser -D -u 1000 appuser
USER appuser
COPY --from=build /app /app
EXPOSE 4242
CMD ["/app"]
The runtime image uses Alpine Linux with a non-root user for security. The final image is typically under 20MB.
Environment Variables
The only configuration is the PORT environment variable:
# Set the port via environment variable
PORT=8080 ./myapp
# In Docker
docker run -p 8080:8080 -e PORT=8080 myapp
If PORT is not set, the server defaults to port 4242.
Platform Guides
The Docker image works with any container platform:
- Fly.io —
fly launchauto-detects the Dockerfile - Railway — connect your repo, Railway builds from the Dockerfile
- Google Cloud Run —
gcloud run deploy --source . - AWS ECS / Fargate — build the image and push to ECR
- Any VPS — copy the binary directly with
scp
Since Gastro builds a static binary with no runtime dependencies, you can also deploy without Docker by copying the binary to any Linux server.
CI: detecting stale .gastro/ (gastro check)
If your repository commits the generated .gastro/ directory (some teams do
for editor convenience; the default gastro new template gitignores it),
add a CI gate to catch contributors who edit a .gastro file without
regenerating:
gastro check
Deleting a
.gastropage or component no longer leaves a stale generated file behind:gastro generate(and thereforegastro dev/gastro build) prunes the orphaned.go/.htmloutput on every run.gastro checkdrift in CI now almost always means a contributor edited a.gastrosource without regenerating, not a leftover from a deleted file.
Every generated Go file starts with
// Code generated by gastro vX.Y.Z. DO NOT EDIT., stamped with the
version of the gastro binary that wrote it ((devel) for local builds).
gastro check also reports the tree as out of date when that stamp differs
from the running binary, even if no other byte would change: generated code
is only guaranteed to work with the gastro version in its header (see
Compatibility). Regenerate after upgrading gastro.
Exit codes:
| Code | Meaning |
|---|---|
0 |
.gastro/ matches what gastro generate would produce |
1 |
Drift detected. Stderr lists the differing files |
2 |
The check itself failed (no .gastro/ directory, generate error, etc.) |
A typical GitHub Actions step:
- name: Verify .gastro is up to date
run: gastro check
You can also wire it into go generate:
//go:generate gastro generate
…and run go generate ./... && git diff --exit-code in CI for the same effect
without the gastro binary needing a separate subcommand. Both approaches are
fine; gastro check is faster (no go invocation) and produces a friendlier
diff summary.
Trimming the committed tree
If you commit .gastro/, you can keep just the Go files and exclude the
transformed template artifacts. They get regenerated on every
gastro generate and contribute most of the diff noise on template-only
edits:
# .gitignore (when committing .gastro/)
.gastro/templates/
.gastro/templates/*.html are not copies of your .gastro source files
— they're build artifacts (frontmatter stripped, {{ wrap }}
rewritten to compiled component calls). External content referenced
by //gastro:embed is baked into the generated .gastro/*.go
handler files at codegen time, not into the templates. The
Go handler files in .gastro/*.go are still committed and remain the
reviewable surface; gastro check continues to catch drift between
source .gastro files and generated Go even with templates/
ignored, because it regenerates and byte-compares the full tree.
static/ is copied into .gastro/static/ for the same //go:embed
reason; it's safe to add .gastro/static/ to the same ignore block if
your static/ directory is large.