Skip to content

Service apps

Not everything you run is something you build. Sometimes you just want an off-the-shelf, prebuilt Docker image — a tool, a vendored service, an observability stack — with MajNet’s placement, ingress, secrets, volumes, and monitoring, but none of the build machinery: no source repo, no CI, and one environment instead of the class gradient.

That is a service: a project-owned app that is an external image plus config.

An ordinary app opts into each class it runs by committing an overlay. A service instead picks a single exposure, which the platform maps to the class that encodes exactly the right trust zone and reachability:

Exposure Runs as Node Access
public production prod custom domain, Cloudflare edge
internal stable private tailnet auto-host (VPN only)

Because a service is a class under the hood, everything else — render, blue-green converge, ingress, secrets, volumes, database, metrics, logs — is the existing app pipeline, reused unchanged.

From a project, New service asks for a name, a digest-pinned image, the exposure, a port, and any secrets. MajNet writes the manifest at apps/<name>/ (a base.yaml plus the single exposure overlay) and records the service in the project’s services: block — that entry is what gives it ownership and a dashboard listing. No repository is created, and org-sync and the image webhook ignore it entirely (it has no repo and isn’t in apps:).

A public service still passes through the production gate: its env/production render PR waits for an admin review before it deploys, exactly like any production change.

Updating a service means editing its pinned image digest (or config) in git — writes-through-git, and the reconciler converges the new digest. Editing config, managing secrets, archiving, and deleting all reuse the normal app actions; archiving additionally drops the services: entry, and deleting purges volumes and databases as usual.

Because a service has no repo or build pipeline, the app-detail view hides the class, deploy, promote, and releases sections — they don’t apply.

A service runs one image. A multi-container stack (say a collector + query + database + UI) is composed from several services sharing the project network via intra-project service discovery — each container resolves its siblings by name — rather than one multi-container service.

MajNet also runs platform-owned image-and-config services — the database engines and Adminer it places for you. Those are hardcoded and platform-managed. A service here is the project-owned counterpart: the same “external image + config” shape, but declared per project and run through the normal app render/converge path.