Deploy Relaticle on your own infrastructure with Docker or manually.
Quick Start
Get Relaticle running in 5 steps:
- Download the compose file:
curl -o compose.yml https://raw.githubusercontent.com/Relaticle/relaticle/main/compose.yml
- Generate an application key:
echo "APP_KEY=base64:$(openssl rand -base64 32)"
- Create a
.envfile with your settings:
APP_KEY=base64:your-generated-key-here
DB_PASSWORD=your-secure-database-password
APP_URL=https://crm.example.com
- Start the containers:
docker compose up -d
- Create your admin account:
docker compose exec app php artisan make:filament-user
Your CRM is now available at {APP_URL}/app.
Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 2 GB | 4 GB |
| CPU | 1 core | 2 cores |
| Disk | 10 GB | 20 GB+ |
| Docker | 20.10+ | Latest |
| Docker Compose | v2.0+ | Latest |
Environment Variables
Required
These must be set or the containers will refuse to start.
| Variable | Description |
|---|---|
APP_KEY |
Encryption key. Generate with openssl rand -base64 32, then prefix with base64:. |
DB_PASSWORD |
PostgreSQL password. Use a strong random value. |
Application
| Variable | Default | Description |
|---|---|---|
APP_NAME |
Relaticle |
Displayed in the browser tab and emails. |
APP_ENV |
production |
Set to production for self-hosting. |
APP_DEBUG |
false |
Set to true only for debugging. Never in production. |
APP_TIMEZONE |
UTC |
Application timezone. |
APP_URL |
http://localhost |
Full URL where Relaticle is accessible. Include the scheme. |
APP_PORT |
80 |
Host port the app container binds to. |
APP_PANEL_DOMAIN |
(empty) | Set for subdomain routing (e.g., app.example.com). Leave empty for path mode (/app). |
PASSKEYS_USER_HANDLE_SECRET |
APP_KEY |
Derives the opaque WebAuthn user handle stored on each authenticator. Set it to its own random value (openssl rand -base64 32) before anyone registers a passkey, so rotating APP_KEY later does not change every user's handle. |
REQUIRE_EMAIL_VERIFICATION |
true |
When false, users sign in without verifying their email. That is useful for self-hosters who haven't configured SMTP yet. The admin you create via make:filament-user is auto-verified regardless, so the default of true is safe for fresh Docker installs. Only set to false if your panel is on a private network: with verification disabled, anyone who can reach the sign-in page can create a working account. |
LOG_CHANNEL |
stderr |
Where logs go. stderr is recommended for Docker. |
LOG_LEVEL |
warning |
Minimum log level. Use debug for troubleshooting. |
| Variable | Default | Description |
|---|---|---|
MAIL_MAILER |
log |
Mail driver: smtp, ses, mailgun, postmark, or log. |
MAIL_HOST |
(empty) | SMTP host (e.g., smtp.mailgun.org). |
MAIL_PORT |
587 |
SMTP port. |
MAIL_USERNAME |
(empty) | SMTP username. |
MAIL_PASSWORD |
(empty) | SMTP password. |
MAIL_ENCRYPTION |
tls |
tls or ssl. |
MAIL_FROM_ADDRESS |
hello@example.com |
Sender email address. |
MAIL_FROM_NAME |
Relaticle |
Sender display name. |
Database and Redis
These are pre-configured in compose.yml and generally don't need changing.
| Variable | Default | Description |
|---|---|---|
DB_DATABASE |
relaticle |
PostgreSQL database name. |
DB_USERNAME |
relaticle |
PostgreSQL username. |
REDIS_PASSWORD |
null |
Redis password. Leave as null for no auth. |
Optional
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID |
Google OAuth client ID for social login. |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret. |
GOOGLE_REDIRECT_URI |
Absolute callback URL (https://your-host/auth/callback/google), or a relative path (/auth/callback/google) to derive the host from the request. |
MICROSOFT_CLIENT_ID |
Microsoft Entra ID client id. The Microsoft button appears only when this is set; register https://your-host/auth/callback/microsoft as the redirect URI. |
MICROSOFT_CLIENT_SECRET |
Microsoft Entra ID client secret. |
SENTRY_LARAVEL_DSN |
Sentry DSN for error tracking. |
FATHOM_ANALYTICS_SITE_ID |
Fathom Analytics site ID. |
AI Assistant
The AI assistant works with cloud providers, a self-hosted server, or both. Models appear in the chat model picker only when their provider is configured. With none configured, the assistant cannot answer.
There are two ways to run a self-hosted model, and you can use either or both:
- Ollama (
OLLAMA_MODEL): the simplest path, with the best tool-calling support. - Any OpenAI-compatible endpoint (
SELF_HOSTED_AI_*): point at vLLM (best for many concurrent users), LM Studio, LocalAI, or a hosted gateway. Exposes multiple models at once.
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY |
(empty) | Enables the Claude models in the chat picker. |
OPENAI_API_KEY |
(empty) | Enables the GPT models in the chat picker. |
OLLAMA_BASE_URL |
http://localhost:11434 |
URL of your Ollama server. From inside a Docker container, localhost is the container itself. Use http://host.docker.internal:11434 (or your host's LAN IP) to reach Ollama running on the host. |
OLLAMA_MODEL |
(empty) | Ollama model tag (e.g. qwen3:14b). Setting this adds the model to the chat picker. |
SELF_HOSTED_AI_URL |
(empty) | Base URL of an OpenAI-compatible endpoint (e.g. http://localhost:8000/v1). |
SELF_HOSTED_AI_KEY |
(empty) | API key for that endpoint, if it requires one (blank for most local servers). |
SELF_HOSTED_AI_MODELS |
(empty) | Comma-separated model tags to expose (e.g. qwen3:14b,llama3.1:70b). Each becomes a picker entry. |
Choosing a model. The assistant calls over 30 CRM tools (search, create, update, delete). Only models with strong tool-calling support work reliably. qwen3 and llama3.1:70b-class models are good starting points. Small models tend to claim an action succeeded without actually invoking the tool. Verify a model before trusting it with php artisan chat:models --probe=<id>, which lists the registry and smoke-tests tool-calling against its endpoint (self-hosted models only; cloud models run through the vendor SDK and are declined).
Known limitations with self-hosted models:
- Cloud providers enforce one-write-proposal-at-a-time at the API level; self-hosted models have no equivalent switch, so this is enforced by prompt instructions only. Every write still requires your explicit approval before anything is saved.
- Each response must complete within 120 seconds. If a slow model exceeds it, the turn stops cleanly. Your message stays in the conversation with a "didn't respond within the time limit" note and nothing is silently lost, so you can just retry. To avoid the limit, use a smaller/faster model or a GPU; "thinking" models like
qwen3reason before answering and reach it sooner, so a non-thinking model or a shorter prompt helps for interactive use.
Feature Flags
Toggle features on or off. All are enabled by default. Useful for forks and custom deployments that want to disable specific functionality without modifying code.
| Variable | Default | Description |
|---|---|---|
RELATICLE_FEATURE_ONBOARD_SEED |
true |
Seed demo data (sample companies, contacts, tasks) when a new team is created. Set to false to start with an empty workspace. |
RELATICLE_FEATURE_SOCIAL_AUTH |
true |
Enable Google and Microsoft social login. Set to false to use only email, password, and passkey sign-in. |
RELATICLE_FEATURE_DOCUMENTATION |
true |
Enable the /developers and /help documentation module. Set to false to remove documentation routes and navigation links. |
Architecture
The Docker setup runs 5 containers:
| Container | Image | Purpose |
|---|---|---|
| app | ghcr.io/relaticle/relaticle:latest |
Web server (nginx + PHP-FPM) on port 8080. Runs migrations automatically on startup. |
| horizon | ghcr.io/relaticle/relaticle:latest |
Queue worker powered by Laravel Horizon. Processes background jobs. |
| scheduler | ghcr.io/relaticle/relaticle:latest |
Runs schedule:work for recurring tasks (e.g., cleanup, notifications). |
| postgres | postgres:17-alpine |
PostgreSQL 17 database. |
| redis | redis:7-alpine |
Cache, sessions, and queue backend. Runs with append-only persistence. |
Volumes
| Volume | Purpose |
|---|---|
postgres |
Database files. Back this up. |
redis |
Redis persistence data. |
storage |
Uploaded files and application storage. Back this up. |
Networking
The app container listens on port 8080 internally and maps to APP_PORT (default 80) on the host. All containers communicate through Docker's internal network. Only the app container exposes a port to the host.
Creating Your Admin Account
After starting the containers, create your first admin user:
docker compose exec app php artisan make:filament-user
When prompted to pick a panel, choose app, then enter a name, email, and password. Once created, access the CRM panel at {APP_URL}/app. (To create the instance-wide system administrator instead, see the next section.)
Note: By default the CRM panel is available at the /app path. To use subdomain routing instead (e.g., app.example.com), set the APP_PANEL_DOMAIN environment variable.
System Administrator Account
The sysadmin panel at {APP_URL}/sysadmin is a separate, instance-wide admin surface for managing every team and user on your installation. To create a system administrator, you can either pick sysadmin from the make:filament-user panel prompt, or use the dedicated command:
docker compose exec app php artisan sysadmin:create
Reverse Proxy and SSL
The app container serves HTTP on port 8080 internally. Place a reverse proxy in front to handle SSL termination and route traffic to the container.
SSL is not optional if you want passkeys: browsers run WebAuthn over HTTPS only, and a passkey is bound to the host it was registered on. That host is APP_PANEL_DOMAIN when subdomain routing is on, and the APP_URL host otherwise, so serve the panel from one hostname and keep it stable.
Nginx
server {
listen 443 ssl http2;
server_name crm.example.com;
ssl_certificate /etc/letsencrypt/live/crm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/crm.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:80;
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;
}
}
server {
listen 80;
server_name crm.example.com;
return 301 https://$server_name$request_uri;
}
Caddy
crm.example.com {
reverse_proxy 127.0.0.1:80
}
Caddy handles SSL certificates automatically via Let's Encrypt.
Traefik
Add these labels to the app service in your compose.yml:
labels:
- "traefik.enable=true"
- "traefik.http.routers.relaticle.rule=Host(`crm.example.com`)"
- "traefik.http.routers.relaticle.entrypoints=websecure"
- "traefik.http.routers.relaticle.tls.certresolver=letsencrypt"
- "traefik.http.services.relaticle.loadbalancer.server.port=8080"
Note: When using a reverse proxy, set APP_URL to your public HTTPS URL (e.g., https://crm.example.com). The app trusts X-Forwarded-* headers from RFC1918 private networks, loopback, and IPv6 ULA/link-local, which covers Coolify/Dokploy/Traefik on a Docker network and reverse proxies on the host. Headers from public IPs are rejected, preventing spoofing.
Deploying on Dokploy
Dokploy is an open-source deployment platform. Here's how to deploy Relaticle on it.
1. Create a Project
In the Dokploy dashboard, click Create Project and give it a name (e.g., "Relaticle").
2. Add a Compose Service
- Inside your project, click Create Service and select Compose
- Set the source to Raw and paste the contents of the compose.yml
- Click Create
3. Set Environment Variables
In the service's Environment tab, add the required variables:
APP_KEY=base64:your-generated-key-here
DB_PASSWORD=your-secure-database-password
APP_URL=https://crm.yourdomain.com
APP_PORT=8080
Generate your APP_KEY with:
echo "base64:$(openssl rand -base64 32)"
Note: Set APP_PORT=8080 so the container maps port 8080:8080, avoiding conflicts with Dokploy's own port 80.
4. Deploy
Click Deploy. Dokploy will pull the images and start all 5 containers. Wait for health checks to pass.
5. Configure Domain
- Go to the Domains tab of the
appservice - Add your domain (e.g.,
crm.yourdomain.com) - Set the container port to
8080 - Enable HTTPS (Dokploy handles Let's Encrypt automatically)
6. Create Admin User
Open the Dokploy terminal for the app container and run:
php artisan make:filament-user
Your Relaticle instance is now live at https://crm.yourdomain.com/app.
Deploying on Coolify
Coolify is an open-source, self-hostable platform for deploying applications.
1. Create a New Project
In the Coolify dashboard, click New Project and give it a name.
2. Add a Docker Compose Resource
- Click Add Resource in your project
- Select Docker Compose
- Choose Empty as the source, then paste the contents of the compose.yml
3. Configure Environment Variables
In the resource's Environment Variables section, add:
APP_KEY=base64:your-generated-key-here
DB_PASSWORD=your-secure-database-password
APP_URL=https://crm.yourdomain.com
4. Set Up Domain
- Go to the app service's Settings
- Set your domain (e.g.,
crm.yourdomain.com) - Coolify will automatically provision an SSL certificate
5. Deploy
Click Deploy. Coolify will pull the images, create containers, and start the services.
6. Create Admin User
Use Coolify's Terminal feature to run in the app container:
php artisan make:filament-user
Access your CRM at https://crm.yourdomain.com/app.
Upgrading
1. Back Up Your Data
Always back up before upgrading. See the Backup and Restore section below.
2. Pull the Latest Images
docker compose pull
3. Restart the Containers
docker compose up -d
Database migrations run automatically on startup. The app container will apply any pending migrations before serving traffic.
Note: Check the release notes before upgrading. Breaking changes will be documented there.
Backup and Restore
Database Backup
docker compose exec postgres pg_dump -U relaticle relaticle > backup-$(date +%Y%m%d).sql
Database Restore
docker compose exec -T postgres psql -U relaticle relaticle < backup-20260320.sql
Storage Backup
Back up the storage volume which contains uploaded files:
docker compose cp app:/var/www/html/storage/app ./storage-backup
Automated Backups
Add a cron job to back up daily:
0 3 * * * cd /path/to/relaticle && docker compose exec -T postgres pg_dump -U relaticle relaticle | gzip > /backups/relaticle-$(date +\%Y\%m\%d).sql.gz
Manual Deployment
If you prefer not to use Docker, you can deploy Relaticle directly on a server.
Requirements
- PHP 8.5+ with extensions: pdo_pgsql, gd, bcmath, mbstring, xml, redis
- PostgreSQL 17+
- Redis 7+
- Node.js 22+
- Composer 2+
- Nginx or Apache
- Supervisor (for queue workers)
Installation
- Clone the repository:
git clone https://github.com/Relaticle/relaticle.git /var/www/relaticle
cd /var/www/relaticle
- Install dependencies:
composer install --no-dev --optimize-autoloader
pnpm install --frozen-lockfile && pnpm run build
- Configure the environment:
cp .env.example .env
php artisan key:generate
Edit .env with your database credentials, mail settings, and APP_URL.
- Set up the database:
php artisan migrate --force
php artisan storage:link
- Set permissions:
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
- Create your admin user:
php artisan make:filament-user
Nginx Configuration
server {
listen 80;
server_name crm.example.com;
root /var/www/relaticle/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Supervisor for Horizon
Create /etc/supervisor/conf.d/relaticle-horizon.conf:
[program:relaticle-horizon]
process_name=%(program_name)s
command=php /var/www/relaticle/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/relaticle/storage/logs/horizon.log
stopwaitsecs=3600
Then start it:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start relaticle-horizon
Scheduler Cron
Add to www-data's crontab:
* * * * * cd /var/www/relaticle && php artisan schedule:run >> /dev/null 2>&1
Troubleshooting
"APP_KEY is required" error on startup
The APP_KEY environment variable is not set. Generate one:
echo "base64:$(openssl rand -base64 32)"
Add it to your .env file and restart:
docker compose up -d
Container restart loops
Check the logs:
docker compose logs app --tail 50
Common causes:
- Missing required environment variables (
APP_KEY,DB_PASSWORD) - PostgreSQL not ready yet (the app waits for a health check, but custom configs may skip this)
- Insufficient memory (need at least 2 GB)
500 errors after deployment
- Check
APP_DEBUG=truetemporarily to see the full error - View logs:
docker compose logs app --tail 100 - Ensure
APP_URLmatches your actual domain (including scheme) - Run
docker compose exec app php artisan optimize:clearto clear all caches
Email not sending
- Verify
MAIL_MAILERis set tosmtp(notlog) - Check your SMTP credentials are correct
- Test with:
docker compose exec app php artisan tinker --execute "Mail::raw('Test', fn(\$m) => \$m->to('you@example.com')->subject('Test'))" - Check logs:
docker compose logs app | grep -i mail
Horizon not processing jobs
- Check Horizon status:
docker compose exec app php artisan horizon:status - Verify the horizon container is running:
docker compose ps - Check horizon logs:
docker compose logs horizon --tail 50 - Restart Horizon:
docker compose restart horizon
Permission errors on storage
docker compose exec app chown -R www-data:www-data /var/www/html/storage
docker compose exec app chmod -R 775 /var/www/html/storage
Database connection refused
- Verify PostgreSQL is running:
docker compose ps postgres - Check PostgreSQL logs:
docker compose logs postgres --tail 20 - Ensure
DB_PASSWORDmatches between the app and postgres containers (both read from the same.envvariable by default)