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.
Exposure, not a class
Section titled “Exposure, not a class”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.
Creating a service
Section titled “Creating a service”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, archiving, deleting
Section titled “Updating, archiving, deleting”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.
Single image per service
Section titled “Single image per service”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.
Services vs. platform services
Section titled “Services vs. platform services”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.