# Planned installation (not released)

**PeltyCMS is not released yet.** There is no public download, image or installer today. This page describes how installation is planned to work at release, and the details may change.

At release, PeltyCMS is planned to be one server binary, `pelty-server`, plus a PostgreSQL 16+ database. The binary serves the API, the built frontend and these docs, runs the database migrations on start and runs the background scheduler, so no separate cron or worker is needed.

## Requirements

- PostgreSQL 16 or newer.
- A domain with TLS in front of PeltyCMS (a reverse proxy such as nginx or Caddy). The server listens on plain HTTP.
- For building from source: Rust with the `wasm32-wasip2` target, Node.js 20+ with pnpm, and GNU make.

## Docker Compose (planned)

The repository ships a `Dockerfile` and a `docker-compose.yml`. Put the secrets in a `.env` file next to the compose file (never commit it), then start it and create the first admin:

```bash
docker compose up -d
docker compose run --rm app admin create you@example.com
```

_docker-compose.yml_

```yaml
# UNVERIFIED: statically reviewed, never run (no Docker on the dev box).
# Put secrets in a .env file next to this file (never commit it):
#   POSTGRES_PASSWORD=<long random>
#   PELTY_ENCRYPTION_KEY=<32-byte key, base64>   # encrypts server API keys / gateway secrets
#   PELTY_PUBLIC_URL=https://billing.example.com
# Optional: PELTY_SMTP_URL, PELTY_MAIL_FROM, PELTY_COOKIE_SECURE (default true here),
#   PELTY_AUTH_RATE_PER_MINUTE, PELTY_PROVISION_ALLOW_LINK_LOCAL (keep unset).
# First admin: docker compose run --rm app admin create you@example.com
services:
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: pelty
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
      POSTGRES_DB: peltycms
    volumes: [pgdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pelty -d peltycms"]
      interval: 5s
      retries: 10
  app:
    build: .
    restart: unless-stopped
    depends_on:
      db: { condition: service_healthy }
    environment:
      DATABASE_URL: postgres://pelty:${POSTGRES_PASSWORD}@db:5432/peltycms
      PELTY_ENCRYPTION_KEY: ${PELTY_ENCRYPTION_KEY:-}
      PELTY_PUBLIC_URL: ${PELTY_PUBLIC_URL:-http://localhost:8080}
      PELTY_SMTP_URL: ${PELTY_SMTP_URL:-}
      PELTY_MAIL_FROM: ${PELTY_MAIL_FROM:-}
      PELTY_COOKIE_SECURE: ${PELTY_COOKIE_SECURE:-true}   # set false only for plain-HTTP local testing
    read_only: true
    tmpfs: [/tmp]
    security_opt: ["no-new-privileges:true"]
    cap_drop: [ALL]
    # Bind to localhost; put a TLS reverse proxy in front.
    ports: ["127.0.0.1:8080:8080"]
volumes:
  pgdata:
```

The image runs `pelty-server serve`, listens on `0.0.0.0:8080` (`PELTY_BIND`), and has a health check on `/healthz`. The compose file binds it to `127.0.0.1:8080` so only your reverse proxy can reach it.

## From source

```bash
make build      # frontend, server binary, addon zips and these docs
./target/release/pelty-server migrate
./target/release/pelty-server admin create you@example.com
./target/release/pelty-server serve
```

`admin create` reads the password from `PELTY_ADMIN_PASSWORD` or the first line of standard input. `make help` lists every target; see [CLI](developers/cli.md) for all commands, including the WHMCS, Blesta and Paymenter importers.

## Required configuration

Only a few environment variables are needed to run in production:

| Variable | Default | Description |
|---|---|---|
| `DATABASE_URL` |  | PostgreSQL connection URL (`postgres://…`). Required. |
| `PELTY_BIND` | `"0.0.0.0:8080"` | Listen address. |
| `PELTY_COOKIE_SECURE` | `true` | Set the `Secure` flag on cookies. Must be true behind HTTPS. |
| `PELTY_ENCRYPTION_KEY` |  | Base64 32-byte key for secrets at rest (TOTP). No default: 2FA enrolment is refused while unset. |
| `PELTY_PUBLIC_URL` | `"http://localhost:8080"` | Absolute base URL used in email links, e.g. `https://billing.example.com`. |

`PELTY_ENCRYPTION_KEY` encrypts stored secrets (server API keys, gateway and addon secrets). Keep it safe and keep it the same across restarts: secrets saved with one key cannot be read with another.

Everything else (company details, currencies, mail, gateways, tax, security policy) is configured in the admin area and stored in the database. Continue with [First-run setup](admin-setup.md).

## All environment variables

| Variable | Default | Description |
|---|---|---|
| `DATABASE_URL` |  | PostgreSQL connection URL (`postgres://…`). Required. |
| `PELTY_ADDONS_DIR` |  | Default `data/addons`; an existing `./addons` (the pre-restructure default) keeps working so older installs are not orphaned. |
| `PELTY_ADDONS_DIST` | `"extensions/addons/dist"` | Directory with the bundled first-party addon zips (default `extensions/addons/dist`). |
| `PELTY_ADDON_DATA_DIR` | `"data/addons"` | Root of per-addon data directories (default `data/addons`), one `<root>/<id>` each. |
| `PELTY_ADDON_MAX_FUEL` |  | Admin caps for addon limits: memory MiB, timeout seconds, fuel (defaults: the built-in maxima). |
| `PELTY_ADDON_MAX_MEMORY_MIB` |  | Admin caps for addon limits: memory MiB, timeout seconds, fuel (defaults: the built-in maxima). |
| `PELTY_ADDON_MAX_TIMEOUT_SECS` |  | Admin caps for addon limits: memory MiB, timeout seconds, fuel (defaults: the built-in maxima). |
| `PELTY_ADMIN_PASSWORD` |  | Password for `admin create` (otherwise the first line of stdin). |
| `PELTY_AUTH_RATE_PER_MINUTE` | `10` | Max auth attempts per IP per minute. |
| `PELTY_BACKUP_DIR` | `"backups"` | Where database backups are written (default `backups`). |
| `PELTY_BIND` | `"0.0.0.0:8080"` | Listen address. |
| `PELTY_COOKIE_SECURE` | `true` | Set the `Secure` flag on cookies. Must be true behind HTTPS. |
| `PELTY_DB_MAX_CONNECTIONS` | `10` | Database pool size. |
| `PELTY_DEFAULT_LOCALE` | `"en"` | Language for visitors without a preference. |
| `PELTY_DOCKER` |  | `PELTY_DOCKER=1` marks a container: addon exec is disabled. |
| `PELTY_DOCS_DIR` | `"target/docs"` | Folder written by `make docs` (default `target/docs`). |
| `PELTY_DOCS_PUBLIC` |  | `true`/`1` makes `/docs` readable without signing in (default: staff only). |
| `PELTY_ENCRYPTION_KEY` |  | Base64 32-byte key for secrets at rest (TOTP). No default: 2FA enrolment is refused while unset. |
| `PELTY_FRONTEND_DIR` | `"frontend/dist"` | Built frontend served as the SPA. |
| `PELTY_LOCALES_DIR` | `"locales"` | Translation JSON files. |
| `PELTY_LOG_JSON` | `false` | Log as JSON lines instead of text. |
| `PELTY_MAIL_FROM` | `"PeltyCMS <no-reply@localhost>".to_string` | From address for outgoing mail. |
| `PELTY_PG_DUMP` | `"pg_dump"` | `pg_dump` binary used for backups (default `pg_dump` on PATH). |
| `PELTY_PLUGINS_DIR` | `"extensions/plugins"` | Webhook plugin manifests. |
| `PELTY_PROVISION_ALLOW_LINK_LOCAL` |  | `1`/`true` lets provisioning modules reach link-local addresses (off by default). |
| `PELTY_PUBLIC_URL` | `"http://localhost:8080"` | Absolute base URL used in email links, e.g. `https://billing.example.com`. |
| `PELTY_SESSION_ABSOLUTE_HOURS` | `24 * 30` | Sessions end after this many hours regardless of activity. |
| `PELTY_SESSION_IDLE_MINUTES` | `60 * 24` | Sign out after this many idle minutes. |
| `PELTY_SMTP_URL` |  | SMTP URL (`smtps://user:pass@host:465`). Unset: mail is logged, not sent. |
| `PELTY_THEMES_DIR` | `"extensions/themes"` | Theme folders (`theme.json`). |
| `PELTY_VIES_URL` |  | EU VIES VAT check API base URL (default: the public EU endpoint). |
| `PELTY_WASM_CACHE_DIR` |  | Compiled-module cache directory (default `<tmp>/pelty-wasm-cache`). |

Generated from the code; the developer reference [Configuration](developers/configuration.md) also shows which file reads each variable.

