Prerequisites
- Docker and Docker Compose (recommended)
- Python 3.11+ (for local development)
- A vulnerability scanner: Qualys VM, Tenable (cloud, Security Center or Nessus), Rapid7 InsightVM (console or cloud) or OpenVAS/Greenbone
Using Docker Compose (recommended)
No clone and no .env. The compose file needs the web server's config, start script and error pages beside it, so take those from the same archive of the repository, and nothing else from it:
mkdir pyxa && cd pyxa
curl -sSL https://github.com/PYXA-Security/pyxa/archive/refs/heads/main.tar.gz \
| tar -xz --strip-components=1 pyxa-main/docker-compose.yml pyxa-main/nginx
docker compose up -dThe nginx/ files sit outside the application image on purpose: nginx serves the error pages exactly when the application is not answering, so they cannot ship inside it. Building a small PYXA nginx image would fold them back into a single download, and has not been done yet.
Then open https://localhost/ and create the first account. PYXA serves HTTPS from the first start with a certificate it signs itself, so the browser warns once; see HTTPS for replacing it.
docker compose exec django python manage.py createsuperuserOn first start PYXA generates its own DJANGO_SECRET_KEY and ENCRYPTION_KEY into the pyxa_secrets volume and prints where they live. Back that file up. ENCRYPTION_KEY is the only thing that can decrypt your connector credentials, webhook signing secrets, SMTP passwords and AI provider keys, and a docker compose down -v without a copy makes every one of them unreadable:
docker compose cp django:/app/secrets/pyxa.env ./pyxa-secrets.envTo manage those keys yourself instead, set them in the environment: they take precedence and the generated file is never read.
Then sign in. An administrator's first sign-in opens Setup (https://localhost/setup/), which walks through everything between an empty install and a working one, required steps first: the deployment's own checks, backing up the encryption key, your team, your tools, a schedule for each, threat intelligence and the background jobs; then owners, SLAs, email, triage and compliance; then tickets, AI and the rest. Each step reads its state from the data and most have a one-click default. When the required steps are done the page says the platform is ready. Load demo data from the same page to look around first.
Adding a source needs no config files either. Setup asks about each kind of tool, or go to https://localhost/connectors/, click + New Connector, pick Qualys, Tenable, OpenVAS, Rapid7 or a file import, and fill in the form. Then "Sync Now", or from the CLI:
docker compose exec django python manage.py sync_qualys_snapshot --source-id 1sync_tenable, sync_openvas, sync_rapid7 and sync_csv_import are the equivalents for the others.
Changing anything
Everything has a working default; a .env file next to docker-compose.yml overrides whichever ones you name, and is ignored if it does not exist. Copy .env.example for the annotated list. The two most people want first:
DJANGO_ALLOWED_HOSTS=pyxa.internal.example.com # anything else 400s
PYXA_IMAGE=ghcr.io/pyxa-security/pyxa:v1.1.0 # pin, rather than track latestTo see what your configuration actually resolves to, and what PYXA thinks is wrong with it:
docker compose exec django python manage.py preflight -v 2That runs on every container start too, reporting every problem at once rather than failing on whichever one it reached first.
HTTPS
On by default, with nothing to prepare. On its first start nginx generates a self-signed certificate for localhost and every name in DJANGO_ALLOWED_HOSTS into nginx/certs/, serves HTTPS on 443, and redirects port 80 to it. The traffic is encrypted from then on; browsers warn only because nothing they trust vouches for the certificate. Open port 443 on the host's firewall.
To use a certificate from your own authority or a public one, either:
- From PYXA: Administration, HTTPS, select the certificate, and upload a
.pfx/.p12, or a PEM or DER certificate with its key. PYXA checks that the key belongs to the certificate, that it is valid today and which allowed hosts it names before writing anything. - On the host: put
fullchain.pem(the certificate, then its intermediates) andprivkey.pem(unencrypted) innginx/certs/.
Either way nginx loads it within 10 seconds without a restart, and keeps serving the previous one if the new pair does not match. Removing both files goes back to a self-signed certificate. PYXA renews its own self-signed certificate 30 days before it expires and regenerates it when DJANGO_ALLOWED_HOSTS changes; a certificate you installed is never touched, and Setup and preflight warn 30 days before it expires.
Behind a proxy of your own that already terminates TLS, set PYXA_TLS=off and DJANGO_FORCE_HTTPS_COOKIES=True: nginx then serves plain HTTP to that proxy and believes its X-Forwarded-Proto. Once browsers trust your certificate, SECURE_HSTS_SECONDS=31536000 tells them never to use plain HTTP again; leave it unset while the certificate is self-signed, because a browser that has seen it refuses the click-through.
Upgrading from the old migration chain
core's 209 migrations are now one initial migration. A database an earlier build migrated is taken over only once it reached core 0209_snapshot_carried_forward. One that stopped earlier makes migrate refuse with "stopped at core 02xx" before it writes anything, and the django container restarts in a loop. No tagged release carries 0209, so migrate once with a build of commit 1150d74, the last with the old chain, then start the current release again:
# 1. Stop what migrates or reads the schema. db and redis keep running.
docker compose stop django celery-worker celery-worker-bulk celery-beat
# 2. Back up the database (use DB_USER and DB_NAME from .env if you changed them).
docker exec patchwork_db pg_dump -U patchwork -Fc patchwork > pyxa-before-upgrade.dump
# 3. Build the bridge, outside the checkout.
git fetch origin main
git worktree add ../pyxa-bridge 1150d74
docker build -t pyxa:bridge ../pyxa-bridge
# 4. Migrate with it, core only: leave docker-compose.cra.yml out.
# It applies the remaining migrations, then lists core's: the last line
# must be "[X] 0209_snapshot_carried_forward".
PYXA_IMAGE=pyxa:bridge docker compose run --rm --no-deps django python manage.py showmigrations core
# 5. Start the current release as usual, with the CRA overlay if you run it.
docker compose up -d
# 6. Remove the bridge.
git worktree remove ../pyxa-bridge && docker image rm pyxa:bridge && docker builder prune -fThe bridge applies core's remaining migrations to 0209 and nothing else. The current release then removes the old migration records and applies pyxa_cra 0001_initial. If step 4 fails, the dump from step 2 restores with docker exec -i patchwork_db pg_restore -U patchwork -d patchwork --clean < pyxa-before-upgrade.dump.
Working on PYXA itself
docker-compose.dev.yml builds from your checkout and mounts it over the image:
git clone https://github.com/PYXA-Security/pyxa.git && cd pyxa
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --buildTo avoid passing both files every time, set COMPOSE_FILE in your .env. The separator is platform-specific, which is easy to lose an afternoon to:
COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml # macOS, Linux
COMPOSE_FILE=docker-compose.yml;docker-compose.dev.yml # WindowsThen plain docker compose up -d picks up both.
Local Development (without Docker)
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt
# Edit patchwork/settings.py for your database
# (SQLite works for development)
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverGenerated from README.md in the PYXA repository.