Gastro

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:

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 .gastro page or component no longer leaves a stale generated file behind: gastro generate (and therefore gastro dev / gastro build) prunes the orphaned .go/.html output on every run. gastro check drift in CI now almost always means a contributor edited a .gastro source 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.