Skip to main content
PropOps ships as a fully containerised application. Docker Compose brings up the web server, MySQL database, and phpMyAdmin in a single command. You do not need to install PHP, Apache, or MySQL directly on the host.
PropOps is licensed software. Each self-hosted instance requires a valid licence key issued by PropOps Technologies Ltd. Contact hello@propops.app to obtain a licence before deploying.

Minimum server requirements

ResourceMinimumRecommended
CPU2 vCPU4 vCPU
RAM2 GB4 GB
Disk20 GB SSD40 GB+ SSD
OSUbuntu 22.04 LTSUbuntu 22.04 LTS
Docker24.xlatest stable
Docker Composev2.xlatest stable
Open ports80, 44380, 443
Disk usage grows over time as photos, documents, and certificate uploads accumulate. Monitor /uploads and expand storage before it fills. For production deployments, mount a cloud block device (e.g. AWS EBS, Hetzner Volume) at the uploads path.

Prerequisites

Before you begin, ensure the following are installed on the host:
1

Install Docker

Follow the official Docker installation guide for your OS:
# Ubuntu / Debian
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in to apply group membership
2

Install Docker Compose v2

Docker Compose v2 ships as a plugin in modern Docker installations:
docker compose version
# Should output: Docker Compose version v2.x.x
If not available, install the plugin:
sudo apt-get install docker-compose-plugin
3

Point your domain

Create an A record in your DNS provider pointing your domain (e.g. propops.yourcompany.com) to your server’s IP address. This is needed for HTTPS setup later.

Installation

1. Clone the repository

git clone https://github.com/PropOps-Technologies-Ltd/PropOps-Web.git
cd PropOps-Web

2. Create the environment file

Copy the example environment file and populate your values:
cp .env.example .env
nano .env
The following variables are required:
# ─── Database ──────────────────────────────────────────────────────────────
DB_HOST=db
DB_NAME=dynamo_system
DB_USER=dynamo_user
DB_PASS=your_strong_db_password          # Change this
DB_ROOT_PASS=your_strong_root_password   # Change this

# ─── Application ───────────────────────────────────────────────────────────
APP_URL=https://propops.yourcompany.com  # Your public URL — no trailing slash
APP_ENV=production                       # production | development
APP_KEY=                                 # 32-character random string (see below)
APP_DEBUG=false

# ─── Licence ───────────────────────────────────────────────────────────────
LICENCE_KEY=your_licence_key_here        # Issued by PropOps Technologies Ltd

# ─── Mail (SMTP) ───────────────────────────────────────────────────────────
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@mg.yourcompany.com
MAIL_PASSWORD=your_smtp_password
MAIL_FROM_ADDRESS=noreply@yourcompany.com
MAIL_FROM_NAME="PropOps"
MAIL_ENCRYPTION=tls

# ─── Push Notifications (optional) ─────────────────────────────────────────
VAPID_PUBLIC_KEY=                        # Generate with: npm run generate-vapid
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:you@yourcompany.com

# ─── WhatsApp (optional) ────────────────────────────────────────────────────
WHATSAPP_API_URL=
WHATSAPP_API_TOKEN=
WHATSAPP_PHONE_ID=

# ─── AI (optional) ─────────────────────────────────────────────────────────
GEMINI_API_KEY=                          # Google Gemini API key for AI analysis
Generate a 32-character app key:
openssl rand -hex 16

3. Start the containers

docker compose up -d
This pulls images, builds the PHP/Apache container, initialises the database schema, and starts all services. The first run takes a few minutes. Verify all containers are running:
docker compose ps
Expected output:
NAME               IMAGE               STATUS         PORTS
propops-web-1      propops-web         running        0.0.0.0:8080->80/tcp
propops-db-1       mysql:8.0           running        3306/tcp
propops-phpmyadmin phpmyadmin          running        0.0.0.0:8081->80/tcp

4. Run the database migrations

On first boot the schema is applied automatically. For upgrades, apply any pending migrations:
docker compose exec web php scripts/migrate.php

5. Set file permissions

The application needs write access to the uploads directory:
docker compose exec web chown -R www-data:www-data /var/www/html/uploads
docker compose exec web chmod -R 755 /var/www/html/uploads

HTTPS with a reverse proxy

PropOps must be served over HTTPS in production. The recommended approach is to place Nginx with Certbot (Let’s Encrypt) in front of the Docker containers.

Install Nginx and Certbot

sudo apt-get install nginx certbot python3-certbot-nginx

Create the Nginx site config

server {
    server_name propops.yourcompany.com;

    location / {
        proxy_pass         http://127.0.0.1:8080;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 120s;
        client_max_body_size 50M;
    }
}
Save to /etc/nginx/sites-available/propops and enable it:
sudo ln -s /etc/nginx/sites-available/propops /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Issue an SSL certificate

sudo certbot --nginx -d propops.yourcompany.com
Certbot automatically modifies your Nginx config to redirect HTTP to HTTPS and configure the certificate. Certificates auto-renew every 90 days.

Managing the containers

TaskCommand
Start all servicesdocker compose up -d
Stop all servicesdocker compose down
View logs (all)docker compose logs -f
View web logs onlydocker compose logs -f web
Restart web containerdocker compose restart web
Open a shell in the containerdocker compose exec web bash
Apply a database migrationdocker compose exec web php scripts/migrate.php
Clear application cachedocker compose exec web php scripts/clear-cache.php

Upgrading

# 1. Pull the latest code
git pull origin main

# 2. Rebuild and restart containers
docker compose up -d --build

# 3. Apply any new migrations
docker compose exec web php scripts/migrate.php
Always back up your database and uploads directory before upgrading. Migrations that alter the schema cannot be reversed automatically.

Backups

Database backup

docker compose exec db mysqldump \
  -u dynamo_user -p"your_db_password" dynamo_system \
  > backup-$(date +%Y%m%d).sql

Uploads backup

tar -czf uploads-$(date +%Y%m%d).tar.gz uploads/
Schedule both in cron to run nightly:
crontab -e
# Add:
0 2 * * * cd /path/to/PropOps-Web && docker compose exec -T db mysqldump -u dynamo_user -p"your_db_password" dynamo_system > /backups/db-$(date +\%Y\%m\%d).sql
0 3 * * * tar -czf /backups/uploads-$(date +\%Y\%m\%d).tar.gz /path/to/PropOps-Web/uploads/

Firewall rules

At a minimum, expose only the ports your reverse proxy needs:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'   # ports 80 and 443
sudo ufw enable
Do not expose port 3306 (MySQL) or 8081 (phpMyAdmin) directly to the internet. Access phpMyAdmin through an SSH tunnel if needed:
ssh -L 8081:localhost:8081 user@your-server-ip
# Then open http://localhost:8081 in your browser

First-time configuration

Once the application is running:
  1. Navigate to https://propops.yourcompany.com — you will see the PropOps login screen.
  2. Sign in with the default admin credentials created during schema initialisation.
  3. Go to Admin → Settings → General to set your organisation name, logo, and default VAT rate.
  4. Go to Admin → Branches to create your first branch.
  5. Go to Admin → Users to invite staff members and configure roles.
  6. Review Admin → Settings → Email to confirm your SMTP configuration is sending correctly.
PropOps includes a built-in maintenance mode. While performing upgrades or configuration changes you can enable it from Admin → Settings → Maintenance Mode or via the API (POST /api/system/maintenance-mode). Authenticated staff still have access; all other users see a maintenance screen.

Troubleshooting

Check for port conflicts — another process may already be using port 8080 or 3306:
sudo lsof -i :8080
sudo lsof -i :3306
Review container logs for the error:
docker compose logs web
docker compose logs db
Ensure the db container is healthy before the web container starts. Check:
docker compose ps
The db service should show healthy. If it shows starting, wait 30–60 seconds and try again. The web container has a depends_on declaration for db, but depends_on only waits for the container to start, not for MySQL to be ready to accept connections.
Check that the uploads directory is writable by www-data:
docker compose exec web ls -la /var/www/html/uploads
# Owner should be www-data
Also check your Nginx client_max_body_size setting — the default is 1 MB, which is too small for most document uploads. Set it to at least 50M.
Ensure VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT are set in .env. Generate new VAPID keys if needed:
docker compose exec web npm run generate-vapid
HTTPS is required for push notifications — they will not work on plain HTTP.
Test your SMTP configuration from inside the container:
docker compose exec web php scripts/test-email.php you@yourcompany.com
Check APP_URL is set to your public HTTPS URL. Links in emails are built from this value.