User Project#

What Is It?#

A User Project is a folder on your computer that describes your entire self-hosted solution . Think of it as the blueprint for your entire infrastructure — it tells PSW which apps you want, where they should run, how your network is configured, and what secrets (passwords, API keys) are needed.

It’s also a git repository (a version-controlled folder where every change is recorded as a snapshot you can inspect or undo), meaning every change you make is tracked. You can go back in time, see what changed, and even push your project to a remote server (like Forgejo ) to trigger automatic deployments .

Why Does It Exist?#

Without a User Project, you’d have to SSH into servers and manually configure everything — install apps, set up databases, configure networking. If something breaks, you’d have to remember what you did and redo it.

With a User Project, your entire self-hosted solution is defined in simple text files. If your server explodes, you can rebuild everything from scratch just by running PSW against your project folder. It’s your self-hosted solution’s source of truth.

What’s Inside?#

Here’s what a typical User Project looks like:

my-project/
├── project.yml          # Your project's identity (domain, settings)
├── network.yml          # Your network layout (servers, IPs, DNS)
├── state.yml            # PSW's memory of what it has done
│
├── nodes/               # Your physical Proxmox servers
│   └── pve1/            # One folder per Proxmox node (named after the node)
│       ├── hardware.yml # What hardware this node has (GPUs, USB devices)
│       └── storage.yml  # How storage is configured on this node
│
├── services/            # Your apps (one folder per app)
│   ├── postgres/
│   │   └── service.yml
│   ├── traefik/
│   │   └── service.yml
│   └── jellyfin/
│       ├── service.yml                 # which target the app runs on
│       ├── routing/jellyfin-routes.yml # Traefik rule for this app
│       ├── bundles/config.yml          # Bundle declaration → Backrest plan for this app
│       └── monitoring/jellyfin-scrape.yml  # Prometheus scrape target
│
├── secrets/             # Encrypted passwords and API keys
│   ├── infra.yml        # SSH keys, server credentials
│   ├── apps.yml         # App-specific passwords
│   └── providers.yml    # DNS/DHCP provider credentials
│
└── roles/               # App deployment recipes and templates (auto-managed)

Cross-app concerns like routing, backups, and monitoring aren’t configured in one big file — each app owns its own small convention fragment under services/<app>/. The aggregator apps (Traefik, Backrest, Prometheus) collect those fragments at convergence time; the aggregated output directories are .gitignored because they’re derived from the per-app files.

The Files You Care About#

FileWhat It Does
project.ymlYour project’s main config — domain name, provider settings, ACME (automatic SSL certificate) config
network.ymlDefines your infrastructureProxmox nodes, targets, IPs, DNS
services/*/service.ymlOne per app — says which app runs on which target , and whether it’s active or being removed
secrets/*.ymlEncrypted credentials — safe to commit to git because they’re encrypted with SOPS

External systems like your router (OPNsense ) and DNS (Cloudflare ) are configured as providers , not as apps — the backend choice lands in project.yml and the credentials land in secrets/providers.yml.

The Files PSW Manages For You#

FileWhat It Does
state.ymlPSW’s logbook — records every operation (add, remove, deploy) with timestamps
roles/App deployment recipes (app metadata ) and templates copied from the app catalog — you don’t edit these
services/<aggregator>/<output-dir>/Traefik/Prometheus/Backrest config collected from per-app fragments. .gitignored and rebuilt on every convergence run — the per-app files in services/<app>/ are the source of truth.

How Do You Create One?#

The easiest way is through the project launcher : run psw wizard, pick “New Project”, choose a folder, and the Setup Wizard walks you through everything from there — entering your domain, configuring storage, and all the way through bootstrap .

If you prefer the CLI, you can create one manually:

psw -C ~/my-project project init --domain mylab.example.com

This creates the PSW-specific directories (services/, secrets/, roles/, nodes/) and marks the project as ready for use.

How Do You Use It?#

Everything you do with PSW happens in the context of a User Project. You point PSW at your project with the -C flag:

# Add an app
psw -C ~/my-project app add jellyfin

# Check for problems
psw -C ~/my-project project check

# See what's installed
psw -C ~/my-project project status

# Preview what a deployment would do
psw -C ~/my-project project plan

The Git Workflow#

Because your project is a git repo, the typical workflow looks like this:

  1. Make changes locally — add apps, tweak configs
  2. Commit your changesgit commit
  3. Push to Forgejogit push
  4. Automatic deployment — the convergence engine detects the changes and deploys them

This is the GitOps workflow — you never SSH into your servers to make changes. You edit files in your project, push, and PSW takes care of the rest.

Key Ideas#

  • One project = one self-hosted solution — each project describes a complete self-hosted solution
  • Git-tracked — every change is versioned, so you can always roll back
  • Secrets are encrypted — passwords and API keys are stored safely using SOPS encryption
  • Declarative — you describe what you want, and PSW figures out how to get there via convergence
  • Self-contained — given the project files and the SOPS decryption key, you can rebuild your entire self-hosted solution from scratch