Self-hosted CRM backups, upgrades, and security - Relaticle         

Backups, upgrades, and security for a self-hosted CRM
=====================================================

A practical operating playbook for backing up, upgrading, and securing a self-hosted CRM without treating customer data casually.

 Manuk Minasyan · September 29, 2026 · 10 min read

 ![Backups, upgrades, and security for a self-hosted CRM](https://relaticle.com/storage/ink/01M17KWWPXPKX3V7DN7YZGR2PW.png) 

Running your own CRM means owning its operational risks as well as its data. The practical answer is straightforward: back up PostgreSQL, uploaded files, and deployment secrets. Prove that you can restore them. Back up before upgrades and configure the security basics before relying on the instance. The procedure below passed a restore drill on Relaticle's Docker Compose deployment. Apply it to your own configuration before relying on the result.

This is not an argument that self-hosting is free of work. It is an argument for doing the work deliberately. A CRM holds the record of customer relationships and business activity. If the database disappears, or an upgrade leaves it in a state you cannot unwind, the application containers are not the hard part. The data is.

Relaticle uses PostgreSQL, Redis, and a Horizon queue worker, and it can deploy on a single server. Its documented Docker Compose path also supports Dokploy and Coolify. That makes the operating surface manageable, but it does not remove the need for an operating routine. For deployment detail, use the [full self-hosting guide](https://relaticle.com/developers/self-hosting). For the costs and tradeoffs around running your own system, [we covered the money side separately](https://relaticle.com/blog/the-real-cost-of-self-hosting-a-crm).

[](#take-a-consistent-backup-before-changing-the-deployment "Permalink")Take a consistent backup before changing the deployment
-------------------------------------------------------------------------------------------------------------------------------

Back up PostgreSQL, uploaded files, deployment configuration, and the original `APP_KEY`. Encrypted custom fields need that key after recovery.

Use a short maintenance window so database records and uploaded files represent the same application state. Pause inbound integrations and let queued writes finish first.

These commands assume the default PostgreSQL database and user are both `relaticle`. Adapt both names if your deployment uses different values.

PostgreSQL documents [`pg_dump`](https://www.postgresql.org/docs/17/app-pgdump.html) for database backups. The storage copy uses [`docker compose cp`](https://docs.docker.com/reference/cli/docker/compose/cp/).

Run these commands from the directory containing your Compose file and `.env`:

```
set -e
umask 077
recovery_backup="backup-$(date +%Y%m%d-%H%M%S)"
mkdir "$recovery_backup"
docker compose stop app horizon scheduler
docker compose exec -T postgres pg_dump -U relaticle relaticle > "$recovery_backup/database.sql"
docker compose cp app:/var/www/html/storage/app "$recovery_backup/storage"
docker compose images > "$recovery_backup/images.txt"
docker compose up -d

```

If any backup command fails, investigate before upgrading. Confirm the application and workers recover when you restart them.

Record the exact application image digest alongside the backup. Keep recoverable deployment configuration and secrets in a protected recovery store.

Copy the database and storage backups to protected storage outside the application server. A backup beside the live data cannot survive losing that server.

The default Compose file does not schedule backups. Automate this procedure through your platform or scheduler, including failure alerts and retention.

[](#restore-into-a-fresh-isolated-deployment "Permalink")Restore into a fresh, isolated deployment
--------------------------------------------------------------------------------------------------

Restore the same application image first. An upgrade and a recovery should be separate operations.

Prepare a new directory with the saved Compose configuration and original secrets. Use an isolated recovery host with outbound integrations blocked.

Set `APP_URL` to the recovery URL and bind `APP_PORT` to loopback, such as `127.0.0.1:8081`. Clear `APP_PANEL_DOMAIN` and `SYSADMIN_DOMAIN`.

Set `MAIL_MAILER=log` in the recovery configuration. Do not copy production domains or reconnect live webhooks during the drill.

Pin the `app`, `horizon`, and `scheduler` images to the recorded image digest. Preserve the original `APP_KEY` and any separate encryption secrets.

Use a new Compose project name with empty volumes. Never point these commands at your running deployment.

Place the backup's `database.sql` and `storage` directory inside this new directory, then run:

```
set -e
export COMPOSE_PROJECT_NAME="relaticle-restore-$(date +%Y%m%d%H%M%S)"
docker compose up -d --wait postgres redis
docker compose create app
docker compose exec -T postgres psql -v ON_ERROR_STOP=1 -U relaticle relaticle
