Install mise, then run these commands from the repository root:
mise install
mise run check
mise run buildcheck runs Go tests, vet, race tests, node --test against web/*.test.mjs, and govulncheck, in that order. It stops on the first failure. Missing frontend tests are an error. Race tests require a C compiler; Ubuntu's GitHub runner provides one. The vulnerability scan needs network access to Go's public vulnerability database and module proxy. CI also builds the binary, Pages site, and Docker image. Successful runs on master deploy the static app to GitHub Pages; CI does not deploy the Go API.
Go 1.27.1, Node 24.21.0 LTS, Air 1.67.4, govulncheck 1.8.0, and Swagger generator 1.16.4 are pinned. GOTOOLCHAIN=local prevents automatic Go version changes. Builds use read-only module resolution, trim local paths, and omit local VCS metadata. mise run tidy is an explicit maintenance command, never a build dependency. mise run swagger regenerates documentation with the pinned generator and does not upgrade application dependencies. Review its generated diff before committing.
mise run watch watches Go and embedded frontend files without rewriting .air.toml. mise run clean requires trash and moves tmp and air-build.log to Trash. rebuild cleans before building. API_PORT=9000 mise run run overrides the application's default port of 8080.
Versions were checked against the official Go download feed, Go support policy, Node release feed, and Alpine release table on September 16, 2026. Update supported patch versions regularly, rerun check, and review container digests together with their tags. Go 1.27.1 removes the standard-library advisories found in the older toolchain. Chi 5.3.0 addresses GO-2026-5775 and GO-2026-5777; the scanner did not find reachable calls to those affected middleware functions in the baseline application.
The Dockerfile compiles a static binary and copies it into Alpine. Both base images have version tags and immutable manifest digests. The runtime uses UID/GID 10001, listens on port 8080, and probes /health. Frontend assets and generated Swagger Go sources must be present in the build context. The binary embeds the frontend; no Node runtime or docs volume is needed.
To deploy, build and start it on the intended host:
docker compose build --pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 gorack-api
curl --fail http://localhost:4201/healthCompose preserves host port 4201. It applies a read-only filesystem, drops Linux capabilities, blocks privilege escalation, and limits the service to 0.5 CPU, 128 MiB memory, and 64 processes. Go's soft memory target is 96 MiB. Adjust those limits using actual load measurements. The app needs no persistent volume. Logs go to stdout/stderr and rotate across three 10 MiB files.
Place the service behind the existing HTTPS reverse proxy. Set its upstream to port 4201 on the Docker host, or port 8080 on the container network. Public / serves the web UI; /v1/api serves the API. /health remains JSON with status: "ok", service: "gorack-api", and a version string. Use /health for platform probes, not the UI or a calculation request.
Docker health status alone does not restart an unhealthy process. unless-stopped restarts exited containers. Investigate failures through logs and the health endpoint before restarting. Preserve the prior image digest for rollback; redeploy that image through the existing hosting platform if needed.
The CI workflow publishes tmp/pages to
pachev.github.io/gorack after all checks and
builds pass on master. Pull requests build and test the export without
publishing it. To retry a deployment, run the CI workflow manually on master.
Repository Settings > Pages must use GitHub Actions, and the github-pages
environment must allow the master branch.
Run mise run build:pages to export the app locally. The exporter adjusts links,
the manifest, and the service worker for /gorack/, and stamps the offline
cache with a hash of the release assets. It excludes test files. Pages runs
the calculator in the browser and does not host the Go API.
The server derives a release identifier from the embedded web assets and inserts
it into /sw.js. Each release caches its own HTML, scripts, styles, and icons.
Keep these files on the same origin as the app. Copying the raw web/ directory
to another static host requires replacing __GORACK_RELEASE__ in sw.js with a
new identifier whenever any shell file changes.
An open app keeps its current release until the user chooses Update ready, then Update app. Other open tabs offer a reload without interrupting their current set. Equipment presets survive the reload. Offline loading requires one successful online visit over HTTPS or localhost.
The existing nixpack.toml filename is retained. Nixpacks normally discovers nixpacks.toml, so select this file explicitly in the platform's configuration or use:
mise exec github:railwayapp/nixpacks@1.41.0 -- nixpacks plan . --config nixpack.toml
mise exec github:railwayapp/nixpacks@1.41.0 -- nixpacks build . --config nixpack.toml --name gorack:nixpacksThe custom plan disables provider detection, builds with the pinned Go image, and copies only the binary into Alpine. This avoids the older Go versions documented by the default Go provider. Its default application port is 4201; set API_PORT to the port expected by the host. Configure /health, restart policy, resource limits, non-root UID 10001, read-only storage, and log retention in that hosting platform. The Nixpacks file describes a build, not platform deployment policy.
The Dockerfile and Compose configuration supply the full runtime restrictions directly. Nixpacks needs the corresponding host settings; its generated runtime image defaults to root unless the host overrides the user. Nixpacks 1.41.0 successfully generated the plan and built a Linux ARM64 image during preparation. No live Nixpacks deployment has been verified.
.github/workflows/uptime.yml checks https://gorack.pachevjoseph.com/health every 15 minutes, offset from the top of the hour. It checks HTTPS, fails on HTTP errors, and requires JSON status to equal ok. Each request has a five-second connection limit and a 15-second total limit. At most two retries occur, with a 50-second retry budget and a two-minute job timeout. It needs no secrets or paid monitoring account.
The monitor is not active until the workflow is pushed to the default branch and GitHub Actions is enabled. In repository Settings, allow GitHub Actions. In the Actions tab, enable the workflow if needed and use Run workflow once to verify it. In your GitHub notification settings, enable Actions notifications for failed workflows and select the delivery channel you want. Confirm receipt from an actual failed run; adding this file does not configure notifications for you. Forks do not run the production probe because the job is restricted to pachev/gorack.
Scheduled Actions can run late and are not a continuous uptime SLA. GitHub can disable schedules in inactive public repositories. Check the Actions tab periodically and re-enable a disabled schedule. This probe checks public reachability and the health response, not every calculator or browser interaction. It does not page an on-call service or automatically restart the application. GitHub-hosted runner usage follows the repository's existing Actions allowance.
- Missing
web/*.test.mjs: check that the checkout contains the frontend test files. The full check requires them. - Go version mismatch: run
mise installand invoke tasks through mise. Automatic toolchain downloads are disabled. - Health probe fails after deployment: check the port mapping, proxy route, and JSON response. A successful web page response does not establish API health.
- Offline page loads without its controls: check static response headers. Do not apply API CORS middleware or
Vary: Originto the app shell; module-script requests can otherwise miss the prefetched cache. Keep CORS on API routes. - Vulnerability scan cannot reach the network: rerun with access to the Go proxy and vulnerability database. A failed scan is not a clean security result.