Skip to main content

Production Setup

This guide is for bare metal / a VM after you have completed Installation (app key, .env, migrations, Passport keys, seed, frontend build). Prefer containers? Use compose.prod.yaml — it already runs Horizon, Reverb, and the scheduler for you.

Before you continue

Confirm the install checklist is done:
  • APP_KEY set
  • Database migrated
  • QUEUE_CONNECTION=redis and Redis reachable
  • Passport keys present (PASSPORT_* env for Docker prod, or php artisan passport:keys on bare metal)
  • php artisan db:seed + UserSeeder ran
  • storage:link ran if FILESYSTEM_DISK=public
  • npm run build completed

Environment (production values)

VITE_REVERB_* are compiled into the frontend — run npm run build again after changing any REVERB_* value. See Configuration for mail, object storage, and social OAuth.

Optimizations

Run these commands after deployment:

Queue Worker

TryPost uses queues for critical background processing:
  • Publishing posts to social platforms at scheduled times (one social-<platform> queue per enabled network)
  • Sending email notifications
  • Verifying channel connections and refreshing tokens
  • Collecting analytics for Insights (analytics queue)
  • Importing posts made outside TryPost and media from Google Drive, Google Photos, Canva or Unsplash (media-imports queue)
  • Polling RSS feeds (rss-feeds queue); repurpose sources are polled on their network’s social-<platform> queue
  • Delivering webhooks and their retries (webhooks queue)
  • Live updates to the browser (broadcasts queue)
Horizon starts a worker pool for each of these queues from config/horizon.php. Without a running queue worker, scheduled posts will not be published. Use Supervisor to keep Horizon running.

Install Supervisor

Configure Supervisor

Create /etc/supervisor/conf.d/trypost-worker.conf:
Then start it:

Nginx Configuration

Example Nginx configuration:
Both /app/ (browser WebSocket) and /apps/ (Pusher HTTP API) must be proxied to Reverb. Omitting /apps/ causes flaky real-time updates.

SSL Certificate

Use Let’s Encrypt for free SSL:

Scheduled Tasks (Cron)

TryPost uses Laravel’s task scheduler for several recurring jobs. The scheduler must run every minute or scheduled posts won’t publish, expiring tokens won’t refresh, and stuck posts won’t recover. Two jobs also run on the scheduler to close each day’s analytics (23:30 and 00:30 UTC).
posts:prune-history deletes data every night. Published posts older than POST_HISTORY_RETENTION_DAYS are removed together with their media. See Post history retention.
Add the Laravel scheduler to the cron of the user that runs PHP (usually www-data), so the files it creates stay writable by php-fpm and Horizon:
Add this line:
This is required. Without the cron job, scheduled posts will not be published automatically and social tokens will expire silently.

WebSocket Server (Reverb)

TryPost uses Laravel Reverb for real-time updates in the app (posts created, deleted or changing status, post notes, Telegram connections and webhook delivery logs). Add a Supervisor config to keep it running: Create /etc/supervisor/conf.d/trypost-reverb.conf:
Then reload Supervisor:
Proxy /app/ and /apps/ through Nginx as shown above.

File Permissions

Set correct permissions:
Keep storage/oauth-private.key readable only by the app user (typically mode 600).

Checklist

Make sure all of these are in place in production:
If Horizon, Reverb, or the scheduler are not running, TryPost will not function correctly. Scheduled posts won’t publish, notifications won’t send, and the dashboard won’t update in real time.

Monitoring

Consider setting up monitoring for:
  • Server health (CPU, memory, disk)
  • Application errors (Laravel logs in storage/logs/)
  • Queue status (Horizon dashboard at /horizon)
  • WebSocket connections (Reverb logs in storage/logs/reverb.log)

Backups

Regularly backup:
  • Database
  • .env file
  • Passport keys (storage/oauth-*.key or PASSPORT_* env) — losing them invalidates API keys and MCP OAuth grants
  • Uploaded media (if using local storage)
Upgrading asks for a database dump before every migrate --force. The commands below read the database credentials from environment variables. On bare metal, load them from .env first, from the app directory: set -a; . ./.env; set +a. Inside the bundled Compose stack the app container already has them. PostgreSQL
MySQL
On the bundled Compose stack the app container carries both clients and already has those variables, so docker compose -f compose.prod.yaml exec app runs either one as written — as does php artisan db for an interactive session.
The image carries mysql-client alongside pdo_mysql, so the MySQL commands run inside the container.