Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions apps/docs/content/docs/core/autoscaling.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: Autoscaling
description: "Three scaling surfaces — application replicas, live manual scaling, and cluster (node) autoscaling — all driven by the Nomad Autoscaler."
---

import { Callout } from "fumadocs-ui/components/callout";

Nomploy has **three** related but distinct scaling surfaces. The word
"autoscaling" shows up in more than one place, so here's the map first:

| Surface | Where | What it scales |
|---|---|---|
| **Application autoscaling** | Application → **Advanced** → *Autoscaling* card | An app's replica count, automatically, between min/max. |
| **Live scaling** | App → **Advanced** → *Scaling* card, and Compose → **Containers** | The running job's replicas, right now (manual, or reflecting the policy). |
| **Cluster (node) autoscaling** | Nomad → **Autoscaling** tab | The number of **nodes** — provisions/destroys cloud VMs. |

The first two scale *your workload's replicas*; the third scales *the cluster's
machines*. All of them are driven by the Nomad Autoscaler.

## Application autoscaling

Turns an app's fixed replica count into a policy the Nomad Autoscaler manages.

**Application service → Advanced tab → Autoscaling card.** Flip the switch on and
set:

- **Min replicas** (default 1)
- **Max replicas** (default 3)
- **CPU target %** (optional) — e.g. `70`
- **Memory target %** (optional) — e.g. `80`

Set at least one of CPU / memory target (you'll get a validation error
otherwise), and keep max ≥ min. Click **Save**, then **redeploy** the app to
apply — the policy is baked into the job's `scaling` block on the next deploy.
With autoscaling **off**, the fixed replica count is used.

Behind the scenes this emits a Nomad `scaling { min, max, enabled = true, policy
{ … } }` block with `cpu`/`memory` checks against the `nomad-apm` source, using a
`target-value` strategy.

## Live scaling

The **Scaling** card (app → Advanced; compose → Containers, Nomad only) shows
each task group with its current `{running}/{desired}` count and, if it has an
autoscaling policy, its `min–max` range and an **autoscaling** / **manual**
badge. Type a number and click **Scale** to set the count immediately
(`nomad job scale`). Useful for a quick manual bump or to observe what the
autoscaler is doing.

## Compose service autoscaling (`x-nomad-scaling`)

For compose services, add an `x-nomad-scaling` policy to a service to get the
same `scaling` block in the generated job — min/max plus CPU/memory targets —
without the app-level form.

## Cluster (node) autoscaling

The Nomad page's **Autoscaling** tab configures the *node* autoscaler: the cloud
provider **token** and the **SSH key** used to provision and join new machines.
This is also the prerequisite for the Cluster tab's one-click **Add node**. It
scales the fleet of servers/workers, not your app replicas.

### Config knobs

Per autoscaling group (Nomad node pool):

| Knob | Meaning |
|---|---|
| **enabled** | Master switch for the group's reconcile loop. |
| **min / max** | Hard floor / ceiling on the **total** worker count in the pool (manual + autoscaled). |
| **desired** | The current target the loop drives toward, clamped to `[min, max]`. Manual sets, scheduled actions, and reactive pressure all move this. |
| **scale-up CPU % / mem %** | Reserved CPU / memory at or above which the loop nudges the target up (defaults 80 / 75). |
| **scale-down CPU % / mem %** | Reserved CPU / memory at or below which — both, with no blocked evals — it nudges the target down (default 25 / 25). |
| **cooldown** | Minimum seconds between scale actions, so one node moves per cooldown instead of thrashing (default 300). |
| **server type / location** | The cloud VM spec + region provisioned for new autoscaled nodes. |

### How it decides (desired-count model)

Each tick (~60s) the loop, per group:

1. Measures **reserved** CPU/memory — what Nomad has *scheduled* onto the pool's
ready+eligible nodes (not live usage) — and counts **blocked evaluations**
targeting the pool (allocations that couldn't be placed).
2. **Converges to `desired` first.** If `workerCount < desired` it scales **up**;
if `workerCount > desired` it scales **down** — but only ever removes an
**autoscaled** node. If the surplus is all manual/pinned it reports
`none (above desired … but the extras are pinned/manual)`.
3. **Only when `workerCount == desired`** does it evaluate pressure: reserved
CPU/mem over the scale-up threshold (or any blocked eval) nudges `desired +1`;
slack under *both* scale-down thresholds nudges `desired −1` (never below
`min`). Because the nudge only happens *at* target, `desired` never runs more
than one node ahead of reality, and the cooldown paces one change per interval.

Scale-up provisions a cloud VM (hostname-safe name), joins it to the pool, and it
becomes a ready worker. Scale-down drains an autoscaled worker, removes it, and
**destroys its VM** — so idle capacity stops billing.

### Manual (pinned) nodes and the `min` floor

A node added manually / one-click (`autoscaled = false`) is **pinned**: it counts
toward `workerCount` but is **never reclaimed** by scale-down. That interacts with
`min` in a way that's easy to get wrong:

<Callout type="warn" title="min must be ≥ the number of pinned workers">
If a pool has one pinned worker but `min = 0 / desired = 0`, the loop is
permanently in the "`workerCount(1) > desired(0)` but pinned" branch and **never
reaches the pressure check** — so it will *never* scale up under load. Set
`min = 1 / desired = 1` (with the pinned worker as the floor) and the loop sits
"at desired," free to nudge up under load and back down when idle, with **zero
extra idle cost** beyond the node you already run.
</Callout>

### Example: elastic with a pinned baseline

One pinned manual worker, burst up to two more under load:

```
min = 1, desired = 1, max = 3
scale-up CPU ≥ 80% or mem ≥ 75% (or any blocked eval)
scale-down CPU ≤ 25% and mem ≤ 25%
cooldown 300s
```

- **Idle:** stays at the 1 pinned worker; no autoscaled VMs, no extra cost.
- **Load:** reserved CPU crosses 80% → provisions an autoscaled VM (one per
cooldown) up to `max = 3`.
- **Load clears:** reserved CPU/mem fall under 25% → removes autoscaled VMs and
destroys them, back down to the pinned floor of 1.
99 changes: 99 additions & 0 deletions apps/docs/content/docs/core/load-balancer.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: Load Balancer (HA ingress)
description: "Turn the single hub Traefik into a highly-available, active/active Traefik pool with DNS failover, shared certificates, metrics and logs."
---

import { Callout } from "fumadocs-ui/components/callout";

Nomploy's default ingress is a single Traefik on the hub. The **Load Balancer**
tab turns that into a **highly-available, active/active Traefik pool** running on
every node you tag, with DNS-based failover and its own metrics and logs.

Open it from the sidebar (**Load Balancer**). It has three tabs: **Overview**,
**Metrics**, and **Logs**.

## How it works

- **Pool** — a Nomad `system` job (`nomploy-traefik-ha`) runs one Traefik per node
whose Nomad meta has `nomploy_lb = "true"`. The hub is deliberately **excluded**
(it keeps the standalone Traefik), so standing the pool up never clashes on
`:80/:443` there and causes no downtime.
- **Routing** — each instance reads routes from the local Consul catalog, exactly
like the hub, so every app/domain is served by every instance.
- **Certificates** — TLS certs are shared through **Consul KV** (Traefik's `consul`
KV provider). Deploying the pool seeds the current certs from the hub's
`acme.json`; a background loop **re-syncs every 6h** so renewals propagate. The
Pool card lists each served domain with a **days-left** badge.
- **Entry / failover (DNS)** — the pool gets a **generated hostname**
(`lb-<random>.<zone>`) whose **A records are kept equal to the healthy nodes'
public IPs** (health-prune). Point your app domains at the pool by adding a
**CNAME** to that hostname. Failover is DNS-speed (records use a 60s TTL).

## 1. Tag the pool nodes

Give each node you want in the pool the `nomploy_lb` meta — from the Nomad tab's
node tagging UI, or the CLI:

```bash
nomad node meta apply -node-id=<node-id> nomploy_lb=true
```

Leave the hub untagged.

## 2. Deploy the pool

**Load Balancer → Overview → Deploy.** This seeds the certs into Consul KV and
runs the system job on every tagged node. The member list shows each instance,
its public IP, and status. Use **Sync certs** to force a cert re-seed, or **Stop**
to remove the pool (the hub keeps serving).

## 3. Configure DNS (optional but recommended)

Requires a DNS provider in **Settings → DNS Providers** (Cloudflare).

On the **Overview → DNS** card:

1. Pick the DNS provider and zone, then **Generate hostname**.
2. Flip **Manage DNS** on. The controller creates A records for the hostname
pointing at the healthy nodes and keeps them pruned (a 30s loop, plus
**Reconcile now**).
3. **CNAME** your app domains to the generated hostname — the pool already serves
their routes and certs.

<Callout title="Proxied wildcards">
If your zone has a proxied wildcard (`*.example.com`), the explicit A records
nomploy creates are more specific and take precedence, so the hostname resolves
straight to your nodes.
</Callout>

## Metrics

The **Metrics** tab shows pool-wide **throughput** (requests/s split by
2xx/4xx/5xx) and **average latency**, over a selectable range (**1h / 6h / 24h /
7d**), plus a live per-instance table (rate, status classes, latency, health,
in-DNS). Data comes from Traefik's Prometheus endpoint, sampled every 60s into a
rolling 7-day series.

## Logs

The **Logs** tab shows **consolidated** Traefik logs across the whole pool, each
line **tagged with the instance** it came from. The access log is parsed into a
searchable, color-coded table (time, instance, status, method, host/path,
duration, client); switch to **Errors** for the app log. Filter with the search
box, **4xx/5xx only**, or **Pause** the 5s auto-refresh.

## Notes & limits

- After enabling metrics/logs for the first time, **Redeploy** the pool so it
exposes the Prometheus `:8082` entrypoint and the access-log config.
- Public IPs are auto-detected from the Hetzner API (via your **Settings → Cloud**
token); a node with no detected public IP is flagged and won't be published.
- The pool serves TLS but does not issue it — a dormant `letsencrypt` resolver is
defined only so catalog routers validate; issuance/renewal stays on the hub.

<Callout type="info">
With the HA pool in front of **all** domains, Let's Encrypt **HTTP-01** no longer
works (any node may answer the challenge). Switch issuance to **DNS-01** by
configuring a DNS provider and redeploying your apps. See **Settings → DNS
Providers**.
</Callout>
7 changes: 7 additions & 0 deletions apps/docs/content/docs/core/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@
"account-security",
"(Notifications)",
"registry",
"self-hosted-registry",
"secrets-providers",
"dns-providers",
"ssh-keys",
"certificates",
"backups",
"concurrent-builds",
"---Services---",
"overview",
"variables",
"domains",
"applications",
"docker-compose",
"databases",
"nomad-packs",
"(examples)",
"auto-deploy",
"schedule-jobs",
Expand All @@ -58,6 +61,10 @@
"remote-servers/validate",
"---Advanced---",
"cluster",
"load-balancer",
"autoscaling",
"telemetry",
"security",
"---Enterprise---",
"enterprise/index",
"enterprise/license-keys",
Expand Down
46 changes: 46 additions & 0 deletions apps/docs/content/docs/core/nomad-packs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: Nomad Pack deploy
description: "Deploy Nomad Packs from a one-click store gallery or any git registry — a variables form, deep-links, and a post-deploy smoke test, no HCL by hand."
---

import { Callout } from "fumadocs-ui/components/callout";

[Nomad Packs](https://developer.hashicorp.com/nomad/tools/nomad-pack) are
parameterised, reusable job templates. Nomploy can deploy them as a service type —
either hand-picked from a git registry, or **one-click from a built-in store
gallery** — so you get databases, tooling and infra without writing HCL.

## Store gallery (one-click)

The pack **store** lists curated packs from
[`Nomploy/nomad-packs`](https://github.com/Nomploy/nomad-packs) as cards with
**category, icon, stars and a health signal**. Pick one, fill its variables, and
deploy — the store deep-links straight into a pre-configured Create Service flow.

## Deploy a pack manually

Create a **Compose** service and choose type **Nomad Pack**. The Deploy Settings
card (**Nomad Pack**) has:

- **Pack** — the pack name, e.g. `traefik`, `zot`, or `hello_world`.
- **Custom registry (optional)** — a git URL. Leave blank for the
[community registry](https://github.com/hashicorp/nomad-pack-community-registry);
set it to `github.com/Nomploy/nomad-packs` (or your own) and it's added before
the run.
- **Variables (HCL)** — pack variables (`count = 2`, `region = "global"`, …),
passed as `--var-file`. Leave empty to use the pack's defaults.

Click **Save**, then **Deploy** to run the pack (`nomad-pack run`). Stop / delete
tear it down (`nomad-pack destroy`). After a deploy, a **smoke test** reports
whether the pack's allocations came up healthy.

<Callout type="warn">
Pack and registry values are validated to a safe character set, since they become
part of the `nomad-pack` command line.
</Callout>

## Using your own registry

Point **Custom registry** at any git repo that holds packs. Nomploy adds it
(`nomad-pack registry add`) before running, so private/internal pack libraries
work the same way as the community one.
22 changes: 22 additions & 0 deletions apps/docs/content/docs/core/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: Overview
description: "A single cross-project dashboard that aggregates every service, backup, domain and deployment across your whole organization."
---

The **Overview** page (sidebar → **Overview**) is a single, organization-wide
dashboard. Instead of opening each project to see what's running, it aggregates
the whole org into one view.

## What it shows

- **Services** — every application and compose service across all projects, with
its status, so you can spot anything unhealthy at a glance.
- **Domains** — all configured domains in one list, with their target service and
certificate state.
- **Backups** — scheduled database/volume backups across every project and when
each last ran.
- **Deployments** — recent deployment activity org-wide.

Each row links straight through to the underlying service, domain, backup or
deployment, so Overview works as a jumping-off point as well as a status board —
useful once you run more than a couple of projects on the cluster.
Loading
Loading