π¦Applications
Layer 4. First-party code and image builds, separate from the infra layers (
nix/,clusters/,terraform/).The three directories
apps/β deployable services: things with a runtime, a deploy target, and a lifecycle. Browse the directory for the current set.packages/β reusable building blocks other apps import or reference: shared frontend code, TypeScript config, Helm charts. Not deployed on their own.images/β base and tool OCI images: build environments and CI runners, not application services.The line isn't Dockerfile-presence β several
apps/entries have no Dockerfile (Starlark apps, the Hugo site,apps/ddnsd's Nix-only deploy path) and onepackages/entry (charts/) has none either. The line is deploy target: an app ships somewhere (a cluster, a Cloud Function, a static host, a NixOS closure); a package is consumed by an app's build.
The root Bun/Turborepo workspace
package.jsondeclaresworkspaces: ["apps/*", "packages/*"], so every directory underapps/andpackages/is nominally a Bun workspace member β but only the ones with their ownpackage.jsonactually participate inbun install/turbo build. Check for apackage.jsonin the app's directory to know which regime it's in.TypeScript/Bun members today:
apps/hub,apps/slingshot,apps/wiki,packages/agent-web-ui,packages/k6,packages/typescript-config. These getbun run {lint,typecheck,build,test}viaturbo.json, Biome formatting/linting (biome.json), and CI in.github/workflows/typescript.yml.Everything else is standalone and brings its own toolchain, invoked directly (not through
turbo):Go apps carry their own
go.modand are built/tested withgo build/go test, not the Bun workspace.Starlark/Pixlet Tidbyt apps have no build step in this repo at all β see below.
apps/pulsifer.cais a Hugo + Tailwind site with its ownmise.toml(pinnedhugo/tailwindcss), deployed by.github/workflows/pulsifer-ca.yml.apps/agent-webandapps/hermesare pure Dockerfiles (nopackage.json, nogo.mod) β the image build IS the app.
How something becomes a published container
containers.ymltriggers on changes underapps/**,packages/**, orimages/**and runs.github/scripts/detect-containers.sh, which:finds every
Dockerfileunderapps/andimages/(notpackages/)reads an optional
build.jsonnext to it for custom image name/context/build-args/watch-paths (default: image name = directory name, context = directory)requires every discovered image name to appear in exactly one of
.github/containers.json'sbuild(published toghcr.io/jonpulsifer/<image>) orignore(has a Dockerfile, deliberately not published here) lists β an unclassified image fails CI, so a new Dockerfile can't be silently published or silently droppedonly rebuilds images whose watch paths actually changed (or all of them, if the workflow/script/manifest itself changed)
To add a published image: add a
Dockerfile(plus optionalbuild.jsonfor a custom image name or multi-image directory βapps/agent-web/build.jsonproduces the singleai-agentsimage from--build-arg AGENT_SET=full), then add its image name tocontainers.json'sbuildlist. To add an unpublished-but-present Dockerfile (a base layer another imageFROMs, a local-only build), add it toignoreinstead.
The Nix carve-out
apps/ddnsdis Go source vendored in-repo;nix/overlays/ddnsd.nixbuilds it as a Nix package (callPackage ../../apps/ddnsd/package.nix) andnix/system/ddnsd.niximportsapps/ddnsd/module.nixto run it as asystemdservice, configured per-host (zone, token file) withservices.ddnsd.enable. This is how homelab hosts actually runddnsdβ through the NixOS closure, not a container.apps/ddnsdalso has aDockerfileand IS incontainers.json'sbuildlist, soghcr.io/jonpulsifer/ddnsdexists β the README documents it as a general-purpose Cloudflare DDNS client for anyone, container included. The two facts coexist: the image is published for external/portable use; this repo's own deployment path for it is Nix, not that image.
Helm charts: how Flux consumes
packages/charts/packages/charts/holds first-party charts (app,ai-agent) with no Dockerfile of their own. AHelmReleasereferences one by relative path against theinfraGitRepository, e.g.clusters/offsite/apps/hub/helm-release.yaml:chart: spec: chart: packages/charts/app reconcileStrategy: Revision sourceRef: kind: GitRepository name: infra namespace: flux-system
reconcileStrategy: Revisionmeans Flux re-renders the chart whenever theinfraGitRepositoryadvances β no chart version bump or separate chart repo needed. TheHelmReleasesupplies the built container image (e.g.ghcr.io/jonpulsifer/hub:latest@sha256:...) as a value.
Apps consumed by an external server's discovery convention
Some
apps/directories aren't structured for this repo's own tooling β they're shaped to satisfy a convention an external server expects. The Tidbyt/Pixlet apps (apps/wishin,apps/tempest, andapps/rackstat's display half) are Starlark.starfiles at the app directory root, becausetronbyt-server's git-repo app discovery expects exactly that layout (apps/<name>/<name>.star) when pointed at this repo. There's no build step for them in this repo;tronbyt-serverrenders the.stardirectly.Pixlet resolves
load()against the app directory and rejects any path that climbs out of it βload("../../packages/β¦")fails withinvalid module. Shared Starlark underpackages/is therefore not reachable, and each Pixlet app carries its own copy of the fetch-cache-degrade helpers. The convergence the apps do share is behavioural, not a common module: every app returns(data, error)from its fetch and renders a splash on failure, because a blank display is indistinguishable from a dead device.A sibling
.starinside one app directory does load. Nothing uses that today βtronbyt-serveris only known to fetchapps/<name>/<name>.star, so a second file is a live-deployment risk that buys nothing while each app fits in one file.
apps/rackstatis the hybrid case: a Go aggregator (main.go, containerized, deployed by Flux fromclusters/folly/apps/tronbyt/) that serves a JSON snapshot, plusrackstat.starβ a Pixlet app in the same directory that renders that snapshot for the display. One directory, two consumers (Kubernetes runs the Go binary;tronbyt-serverdiscovers the.star).
Linked references 3
Layer 4 β Applications (Architecture/Applications)
What actually runs on the two Kubernetes clusters, and where its manifests live. Cluster mechanics are on Architecture/Kubernetes; first-party source and image builds are on Architecture/Applications.
Applications β first-party services, packages and OCI images, described in Architecture/Applications.