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.26-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

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.