Bootstrap#
What Is It?#
Bootstrap is the one-time setup process that brings your self-hosted solution to life. It takes a bare Proxmox node and turns it into a fully functional platform — with a database, reverse proxy, authentication, a git server, and a web dashboard — all in a single command.
Think of it like building the foundation of a house. You do it once, and everything else (your apps ) gets built on top of it.
Why Does It Exist?#
Before you can deploy apps like Jellyfin or Grafana , you need infrastructure in place:
- A database for apps to store data (PostgreSQL )
- A reverse proxy (a gateway that routes web traffic to the right app) to give every app a clean URL with HTTPS (secure, encrypted web traffic) (Traefik )
- A user directory (LDAP — a shared database of user accounts) so you don’t need separate accounts for every app (LLDAP )
- SSO (Single Sign-On — log in once and access everything) (Authelia )
- A git server to store your user project and trigger automatic deployments (Forgejo )
- A dashboard to see what’s running and monitor your self-hosted solution (PSW Dashboard)
Bootstrap deploys all eight of these core apps in one go, so your self-hosted solution is immediately usable the moment bootstrap finishes.
What Gets Deployed?#
Bootstrap deploys all apps marked bootstrap_only: true in their metadata
. The deployment order is determined by each app’s deploy_priority
— lower values deploy first:
| Priority | App | What It Does |
|---|---|---|
| 0 | PostgreSQL | Shared database — most apps store their data here |
| 5 | Dragonfly | Redis-compatible in-memory data store (session cache for Authelia) |
| 10 | Traefik | Reverse proxy — gives every app a URL like https://jellyfin.yourdomain.ca with automatic HTTPS |
| 20 | LLDAP | User directory — one set of user accounts shared across all apps |
| 30 | Authelia | Single Sign-On — log in once, access everything, with two-factor authentication |
| 40 | Forgejo | Git server — stores your project and triggers deployments when you push changes |
| 50 | PSW Dashboard | Web UI — see what’s running, monitor deployments, check status at a glance |
The order matters: PostgreSQL must be running before other apps can use it, Authelia needs LLDAP for user accounts, and so on. Bootstrap handles this automatically based on the metadata.
What Happens Step by Step?#
Here’s the full journey from “I have a Proxmox server” to “my dashboard is live”:
1. Validate
└── Checks that providers are configured, SSH keys (for secure remote access) exist, Proxmox node is reachable
2. Create [Target](infrastructure.md#targets)
└── Creates a new [managed target](infrastructure.md#managed-targets-type-lxc) on your Proxmox node
└── Boots the target with the static IP planned in `network.yml` — the plan is the truth
└── Records a *squat* reservation with the [DHCP](providers.md#dhcp) [provider](providers.md) so non-PSW devices can't grab the same address
└── Registers it in `network.yml`
3. Sync app roles + generate secrets + stage project files
└── Syncs [app roles](app-metadata.md) from the app catalog
└── Generates [secrets](secrets.md) for all core apps
└── Builds the dashboard image and stages the project files Forgejo will receive later
4. Prepare the **bootstrap target only**
└── Installs packages, locks down SSH, applies sysctls — on the *one* target this bootstrap owns
└── Other targets in `network.yml` are convergence's job; bootstrap leaves them alone (they don't even exist on Proxmox yet)
└── Has to run before step 5: in [ISP-router mode](providers.md#local-dns), the local-DNS plumbing deploys [Podman](https://podman.io/) containers onto this target
5. Prepare local DNS
└── Makes the configured [local-DNS backend](providers.md#local-dns) ready to accept records
└── In [OPNsense mode](providers.md#opnsense_unbound): asserts OPNsense Unbound is running and in **recursive** mode (no upstream forwarders — the privacy guarantee)
└── In [ISP-router mode](providers.md#adguard_home): deploys [AdGuard Home](https://github.com/AdguardTeam/AdGuardHome) + [Unbound](https://www.nlnetlabs.nl/projects/unbound/) recursive sidecar onto the bootstrap target as Podman containers, waits healthy
└── Same orchestrator code in both modes — the backend's `prepare()` does whatever it needs
6. Push [DNS](providers.md#local-dns) records
└── Creates local DNS overrides for every core app's hostname (`auth.<domain>`, `git.<domain>`, …)
└── Has to run before app deploys: Forgejo's setup reconciler waits for `https://auth.<domain>/.well-known/openid-configuration` to resolve — without DNS in place first, that lookup never succeeds
└── See [split-horizon DNS](split-horizon-dns.md)
7. Deploy Core Apps (one by one, in order)
└── PostgreSQL → Dragonfly → Traefik → LLDAP → Authelia → Forgejo → Dashboard
└── Each app waits for the system to settle before deploying the next
8. Wire Everything Together
└── Creates the Forgejo admin account and config repository
└── Connects apps via OAuth/OIDC (authentication protocols for SSO) ([conventions](conventions.md))
9. Install [Convergence](convergence.md)
└── Sets up the automatic deployment engine on the bootstrap target
└── Seeds initial state so convergence knows what's already deployed
└── Enables the [systemd](https://systemd.io/) timer (Linux service scheduler, runs every 5 minutes)
10. Push [User Project](user-project.md) to Forgejo
└── Your project files are now stored on the git server
└── Future changes are deployed automatically via [convergence](convergence.md)When bootstrap is driven from the Setup Wizard’s Launch step
, the wizard also commits the post-bootstrap state (including .psw/converge/state.yml) and pushes it to Forgejo automatically — that way the next convergence
tick sees the exact state bootstrap produced.
The Bootstrap Target#
All core apps
are deployed to a single target
called the bootstrap target
. Which target that is gets recorded in project.yml:
bootstrap:
target: coreThe bootstrap target is defined like any other target
in network.yml:
targets:
core:
type: lxc
node: small
ip: 10.10.0.100
cores: 8
memory: 40960
disk: 200There’s nothing structurally special about it — PSW just needs to know which target hosts the core apps and the convergence engine.
Bootstrap vs Convergence#
Bootstrap and convergence are two halves of the same deployment system:
| Bootstrap | Convergence | |
|---|---|---|
| When | Once, when setting up your self-hosted solution | Continuously, every 5 minutes |
| What | Deploys the 8 core apps | Deploys the apps you add later |
| How | You run it manually | Runs automatically via systemd timer |
| Creates targets? | Yes — creates the bootstrap target | Yes — creates targets for your apps |
| Sets up convergence? | Yes — installs the convergence engine | No — it is the convergence engine |
Think of it this way: bootstrap builds the factory, convergence runs the assembly line.
After bootstrap completes, you never run it again. From that point on, you just:
- Add apps
with
psw app add - Commit and push
- Convergence handles the rest
Tip: You can also
psw app addapps before running bootstrap. Sincepsw app addis a local-only operation (it just writes files to your user project ), it doesn’t require any infrastructure to be running. Bootstrap will then deploy the core apps, and the next convergence run will pick up your pre-added apps.
What You Get After Bootstrap#
The moment bootstrap finishes:
- Your dashboard is live at
https://dashboard.yourdomain.ca - Traefik is routing HTTPS traffic with valid certificates
- Authelia is protecting all routes with Single Sign-On
- Forgejo is hosting your project’s git repository
- Convergence is running and watching for changes
- Your self-hosted solution is ready for you to start adding apps
What If Bootstrap Fails?#
If something goes wrong during bootstrap (network glitch, provider timeout, etc.), you can simply re-run the same command. Bootstrap tracks its progress in a .psw/bootstrap/progress.yml file and skips steps that already completed:
- If the target was already created, it’s reused (same IP, same container)
- If secrets were already generated, that step is skipped
- If apps 1-4 were already deployed, deployment resumes at app 5
- The Forgejo git setup (admin account, tokens, runner) is treated as one atomic step — if any part fails, the whole step retries on re-run
This means a failure at step 10 of 13 doesn’t waste time re-running steps 1-9. Just run the same command again:
psw deploy bootstrap --target coreThe progress file is automatically cleaned up on success and by psw deploy reset. To force a completely fresh start (ignoring any previous progress), use the --clean flag:
psw deploy bootstrap --target core --cleanPrerequisites#
Before you can run bootstrap, you need:
- An initialized user project
— created by
psw node initandpsw project init - A Proxmox node
— at least one physical server defined in
network.yml - Providers
configured — DNS and DHCP providers set up via
psw provider configure - A domain — set in
project.yml
Running Bootstrap#
psw deploy bootstrap --target coreThat’s it. One command, and your self-hosted solution goes from bare metal to a fully functional platform.
Options#
| Flag | Description |
|---|---|
--target, -t | Name for the bootstrap target (required) |
--node, -n | Proxmox node to create target on (default: first registered) |
--cores | CPU cores for the bootstrap target |
--memory | RAM in MB for the bootstrap target |
--swap | Swap in MB for the bootstrap target |
--disk | Root disk size in GB for the bootstrap target |
--clean | Discard previous progress and start from scratch |