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:

PriorityAppWhat It Does
0PostgreSQLShared database — most apps store their data here
5DragonflyRedis-compatible in-memory data store (session cache for Authelia)
10TraefikReverse proxy — gives every app a URL like https://jellyfin.yourdomain.ca with automatic HTTPS
20LLDAPUser directory — one set of user accounts shared across all apps
30AutheliaSingle Sign-On — log in once, access everything, with two-factor authentication
40ForgejoGit server — stores your project and triggers deployments when you push changes
50PSW DashboardWeb 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: core

The 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: 200

There’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:

BootstrapConvergence
WhenOnce, when setting up your self-hosted solutionContinuously, every 5 minutes
WhatDeploys the 8 core appsDeploys the apps you add later
HowYou run it manuallyRuns automatically via systemd timer
Creates targets?Yes — creates the bootstrap targetYes — creates targets for your apps
Sets up convergence?Yes — installs the convergence engineNo — 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:

  1. Add apps with psw app add
  2. Commit and push
  3. Convergence handles the rest

Tip: You can also psw app add apps before running bootstrap. Since psw app add is 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 core

The 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 --clean

Prerequisites#

Before you can run bootstrap, you need:

  1. An initialized user project — created by psw node init and psw project init
  2. A Proxmox node — at least one physical server defined in network.yml
  3. Providers configured — DNS and DHCP providers set up via psw provider configure
  4. A domain — set in project.yml

Running Bootstrap#

psw deploy bootstrap --target core

That’s it. One command, and your self-hosted solution goes from bare metal to a fully functional platform.

Options#

FlagDescription
--target, -tName for the bootstrap target (required)
--node, -nProxmox node to create target on (default: first registered)
--coresCPU cores for the bootstrap target
--memoryRAM in MB for the bootstrap target
--swapSwap in MB for the bootstrap target
--diskRoot disk size in GB for the bootstrap target
--cleanDiscard previous progress and start from scratch