Deployment Guide
Building for Production
pranor build app.pnr -o myservice.exe
The output is a single static binary — no runtime dependencies.
Docker
Generate a Dockerfile:
serv dockerize app.pnr
Or manually:
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o serv main.go
RUN ./pranor build app.pnr -o service
FROM alpine:latest
COPY --from=builder /app/service /service
EXPOSE 8080
CMD ["/service"]
Port Configuration
Priority (highest to lowest):
--portCLI flag:./pranorice --port 9090PORTenv var:PORT=9090 ./pranorice- Config file:
server.port: "9090"inconfig.yml - Source declaration:
server "8080"
TLS / HTTPS
server "443" tls "cert.pem" "key.pem"
Configuration File
Create config.yml in the working directory:
server:
port: "8080"
db:
host: "localhost"
port: "5432"
name: "myapp"
log:
level: "info"
format: "json"
otel:
endpoint: "http://collector:4318"
service: "my-service"
Access in code: config("db.host") → "localhost"
Config Validation
Fail fast on missing required config:
validate {
required "db.host",
required "db.port",
required "app.secret"
}
If any key is missing at startup, the service exits with an error message showing which keys are missing and how to set them.
OpenTelemetry (Tracing & Metrics)
Set environment variables to enable:
OTEL_ENDPOINT=http://localhost:4318 ./pranorice
OTEL_SERVICE_NAME=my-service ./pranorice
Auto-instrumented:
- HTTP routes (method, path, status, duration)
- Database queries (operation, statement)
- Cache operations (GET/SET, key)
- HTTP client calls (method, URL, status)
- Pub/sub messaging (publish/subscribe, topic)
- Scheduled jobs (every/cron, interval)
- External calls (Python/Go extern functions)
Protocol: OTLP/HTTP JSON — compatible with Jaeger, Tempo, Datadog, Honeycomb, etc.
Health Checks
Auto-generated endpoints (no code needed):
GET /health— Returns{"status": "healthy"}GET /ready— Returns{"status": "ready"}GET /metrics— Prometheus-style metrics
Structured Logging
# JSON output (for log aggregators)
LOG_FORMAT=json ./pranorice
# Set level
LOG_LEVEL=debug ./pranorice
Output format (JSON mode):
{"level":"info","message":"Request handled","timestamp":"2024-01-01T00:00:00Z","request_id":"abc123"}
Graceful Shutdown
Serv services handle SIGINT and SIGTERM:
- Stop accepting new connections
- Wait up to 15 seconds for active requests to complete
- Close database connections
- Exit cleanly
Cross-Compilation
Build for different platforms:
GOOS=linux GOARCH=amd64 go build -o serv-linux main.go
./pranor-linux build app.pnr -o service-linux
CI/CD
GitHub Actions and GitLab CI templates are included:
.github/workflows/ci.yml.gitlab-ci.yml
Both compile all examples, run tests, check formatting, and build release binaries on version tags.
Publishing & Distribution
Release Build (All Platforms)
Cross-compile for macOS, Linux, and Windows:
./release-scripts/build-release.sh v1.0.0
This produces archives in release/:
release/
├── serv-darwin-amd64.tar.gz
├── serv-darwin-arm64.tar.gz
├── serv-linux-amd64.tar.gz
├── serv-linux-arm64.tar.gz
└── serv-windows-amd64.zip
Each archive contains serv (or pranor.exe) and pranor-lsp (or pranor-lsp.exe).
GitHub Release
- Tag the release:
git tag v1.0.0 && git push --tags - Create a GitHub Release from the tag
- Upload all archives from
release/ - Note the SHA256 hashes (needed for Homebrew/Scoop):
shasum -a 256 release/*.tar.gz release/*.zip
Homebrew (macOS/Linux)
Formula: release-scripts/homebrew/serv.rb
Setup (one-time):
- Create a Homebrew tap repo:
github.com/user/homebrew-pranor - Copy
serv.rbinto the tap repo - Update SHA256 hashes and download URLs to point to your GitHub release
User install:
brew tap user/serv
brew install serv
Updating for new releases:
- Update
versioninserv.rb - Update SHA256 hashes
- Push to the tap repo
Scoop (Windows)
Manifest: release-scripts/scoop/serv.json
Setup (one-time):
- Create a Scoop bucket repo:
github.com/user/scoop-pranor - Copy
serv.jsoninto the bucket repo - Update the
hashandurlfields with actual release URLs
User install:
scoop bucket add serv https://github.com/user/scoop-pranor
scoop install serv
Updating for new releases:
- Update
version,url, andhashinserv.json - Push to the bucket repo (Scoop's
autoupdatehandles future versions automatically)
VS Code Extension
Publish script: release-scripts/publish-vscode.sh
Prerequisites:
npm install -g @vscode/vsce
First-time setup:
- Get a Personal Access Token from https://dev.azure.com (Marketplace scope)
- Run:
vsce login pranor
Publishing:
cd vscode-support/extension
vsce package # Creates .vsix file
vsce publish # Publishes to VS Code Marketplace
Or use the script:
./release-scripts/publish-vscode.sh
User install (after publishing):
- VS Code: Search "Serv Language Support" in Extensions
- CLI:
code --install-extension pranor.serv-vscode
Docker Base Image
Dockerfile: release-scripts/docker/Dockerfile.base
Build and push:
docker build -t serv:latest -f release-scripts/docker/Dockerfile.base .
docker tag serv:latest ghcr.io/user/serv:latest
docker push ghcr.io/user/serv:latest
User usage:
FROM ghcr.io/user/serv:latest
WORKDIR /app
COPY myservice.pnr .
RUN pranor build myservice.pnr -o service
CMD ["./pranorice"]
Complete Release Checklist
-
Run regression tests:
powershell test_regression.ps1 -
Update version in:
release-scripts/homebrew/serv.rbrelease-scripts/scoop/serv.jsonvscode-support/extension/package.json
-
Cross-compile:
./release-scripts/build-release.sh v1.x.x - Create GitHub Release, upload archives
- Compute SHA256 hashes, update Homebrew formula + Scoop manifest
- Push Homebrew tap and Scoop bucket repos
-
Publish VS Code extension:
./release-scripts/publish-vscode.sh - Build and push Docker image
- Announce release