selfupdate-demo

burrow contrib/selfupdate end-to-end demo
Built on Burrow (modular Go web framework), Tailwind v4 (utility-first CSS, dark mode via prefers-color-scheme), htmx (server-driven interactivity), SQLite via Den (pure Go, no CGO).
Quick Start
# Bootstrap the project (mise install + go mod tidy + git hooks)
mise run setup
# Run the development server with live reload (auto-generates .env on first run)
mise run dev
Server: http://localhost:8080.
Requirements
- mise — installs every other tool pinned in
.mise.toml
Development
mise tasks lists every task. The dev loop:
| Task |
What it does |
mise run dev |
burrow dev: watches the project, rebuilds Tailwind CSS, restarts the Go app on every save — sequentially, in one process. |
mise run test |
go test ./... via tparse |
mise run lint |
golangci-lint |
mise run fmt |
gofmt + goimports |
mise run vuln |
govulncheck |
On first mise run dev an .env file is generated with persistent SESSION_HASH_KEY and CSRF_KEY (mode 0600) so sessions and CSRF tokens survive reloads. The file is gitignored. Edit it to add your own dev-time variables — burrow dev reads the whole file as a dotenv and injects the keys into the app's environment.
Project Structure
├── cmd/
│ └── <name>/ # Server entry point
│ └── main.go # NewServer + cli wiring
├── internal/
│ └── app/ # Shell app: layout, homepage, CSS bundle (Pattern B)
│ ├── app.go # HasTemplates, HasStaticFiles, NavItems, Routes
│ ├── static/ # Tailwind output (app.min.css, gitignored)
│ └── templates/
│ ├── app/
│ │ ├── layout.html # Site layout with navbar, alerts, htmx
│ │ └── icons.html # Inline-SVG icon defines
│ ├── error/ # Tailwind-styled overrides for 403/404/500/...
│ │ └── errors.html
│ └── pages/
│ └── home.html
├── tailwind.css # Tailwind entrypoint (imports source list)
├── Dockerfile # Multi-arch image (linux/amd64 + linux/arm64)
├── go.mod # Pinned burrow + Den versions
├── .mise.toml # Tool pins + task runner
├── .golangci.yml # Linter config
└── .goreleaser.yaml # Release config (archives + Docker image)
internal/app/ follows the Pattern B project layout from Burrow's Tailwind guide: the shell app owns the layout templates, the compiled Tailwind CSS, and the project-level icon defines, all served under the /static/app/ URL prefix.
Releases
Push a v* tag to trigger the release workflow:
git tag v0.1.0
git push origin v0.1.0
The workflow runs mise run release (= goreleaser release --clean) which in one go:
- Builds binaries for linux / darwin / windows × amd64 / arm64
- Packages them as
tar.gz (Linux) / zip (macOS, Windows) + checksums.txt
- Builds a multi-arch Docker image (
linux/amd64 + linux/arm64)
- Uploads the archives to GitHub Releases (with auto-generated notes grouped by
feat: / fix:)
- Pushes the image to GitHub Container Registry as
ghcr.io/<user>/<project>:v<version> and :latest
Local alternatives:
| Task |
When |
mise run release-snapshot |
Sanity-check the goreleaser config without publishing or building Docker |
mise run release-no-docker |
Cut a binaries-only release (e.g. when ghcr auth is missing) |
mise run release |
Same as the CI workflow — needs an authenticated gh + Docker login to ghcr.io |
Run the published Docker image:
mkdir -p data && sudo chown 65532:65532 data # distroless nonroot UID
docker run --rm -p 8080:8080 \
-v $PWD/data:/data \
-e DATABASE_DSN=sqlite:////data/app.db \
ghcr.io/<user>/<project>:latest
License
MIT — see LICENSE.