13 — Deployment Guide
Last updated: 2026-05-30
Cara deploy crmoffice ke production. Tiga target documented: Forge, self-hosted Ubuntu, dan Docker Compose.
0. Prasyarat Produksi
| Requirement | Minimal |
|---|---|
| PHP | 8.3+ (required: bcmath, curl, dom, fileinfo, gd, intl, mbstring, mysql, redis, xml, zip) |
| MySQL | 8.0+ (MySQL 8.4 recommended) |
| Redis | 7.0+ (required for Horizon, queue, cache; AOF persistence on) |
| Meilisearch | v1.10+ (optional — for full-text search; fallback: SCOUT_DRIVER=database) |
| Nginx | 1.24+ (or Apache 2.4 with mod_rewrite) |
| Supervisor | 4.2+ (queue worker + Horizon daemon) |
| Composer | 2.x |
| Node.js | 20 LTS (for Vite asset build) |
| Certbot | 2.x (Let's Encrypt SSL auto-renew) |
1. Quick Compose (paling cepat untuk POC)
git clone <repo> crmoffice
cd crmoffice
cp .env.example .env
php artisan key:generate # atau echo "APP_KEY=base64:$(openssl rand -base64 32)" >> .env
docker compose up -d
docker compose exec app php artisan migrate --seed
Browse http://localhost:8080 → login owner@crmoffice.local / password → ganti password segera.
Services:
app(Nginx + PHP-FPM 8.3) — port 8080mysql(MySQL 8.4)redis(Redis 7)meilisearch(v1.10) — port 7700queue-worker(Horizon-equivalent viaqueue:work)scheduler(cron replacement)
Volumes persisted: mysql_data, redis_data, meili_data.
2. Laravel Forge (recommended)
- Provision server (DigitalOcean / AWS / Vultr) → minimum 2GB RAM
- Install MySQL 8, Redis 7, PHP 8.3 (Forge does this)
- Create site → connect Git repo → branch
main - Set env vars in Forge UI (jangan commit
.env):APP_NAME=crmoffice APP_ENV=production APP_DEBUG=false APP_URL=https://your-domain.com APP_KEY=base64:... DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_DATABASE=crmoffice DB_USERNAME=forge DB_PASSWORD=... CACHE_STORE=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis REDIS_CLIENT=predis SCOUT_DRIVER=meilisearch MEILISEARCH_HOST=http://127.0.0.1:7700 MEILISEARCH_KEY=... - Deployment script:
cd $FORGE_SITE_PATH git pull origin $FORGE_SITE_BRANCH composer install --no-dev --optimize-autoloader npm ci && npm run build php artisan migrate --force php artisan optimize php artisan filament:cache-components if [ -f artisan ]; then $FORGE_PHP artisan queue:restart fi - Daemon: configure Forge daemon
php artisan queue:work --tries=3 --timeout=60(or install Horizon) - Scheduler: enable Forge scheduler (cron
* * * * * php artisan schedule:run) - SSL: Let's Encrypt via Forge → free + auto-renew
- Horizon (Linux):
composer require laravel/horizonthen publish + supervisor config
3. Self-Hosted Ubuntu 22.04 / 24.04
# System packages
apt update && apt install -y nginx mysql-server-8.0 redis-server php8.3-fpm php8.3-mysql php8.3-mbstring php8.3-xml php8.3-curl php8.3-bcmath php8.3-gd php8.3-intl php8.3-zip php8.3-redis composer nodejs npm git supervisor certbot python3-certbot-nginx
# Clone + setup
cd /var/www && git clone <repo> crmoffice && cd crmoffice
composer install --no-dev --optimize-autoloader
cp .env.example .env && php artisan key:generate
nano .env # configure DB credentials, APP_URL, Redis, Meilisearch
npm ci && npm run build
php artisan migrate --seed --force
php artisan optimize
# Nginx — copy production config
cp deploy/nginx.conf /etc/nginx/sites-available/crmoffice
nano /etc/nginx/sites-available/crmoffice # replace yourdomain.com with actual domain
ln -s /etc/nginx/sites-available/crmoffice /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
# SSL — Let's Encrypt via certbot
certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Then uncomment the HTTPS server block in /etc/nginx/sites-available/crmoffice
nginx -t && systemctl reload nginx
# Permissions
chown -R www-data:www-data /var/www/crmoffice/storage /var/www/crmoffice/bootstrap/cache
# Supervisor — queue workers + Horizon + scheduler
cp deploy/supervisor.conf /etc/supervisor/conf.d/crmoffice.conf
supervisorctl reread && supervisorctl update
supervisorctl start crmoffice-worker-default:*
supervisorctl start crmoffice-worker-high:*
supervisorctl start crmoffice-horizon:*
supervisorctl start crmoffice-scheduler:*
3a. Nginx Config (deploy/nginx.conf)
Production Nginx config sudah disediakan di deploy/nginx.conf. Fitur:
- Rate limiting: 10 req/s untuk
/admin/login - Gzip: compression text/JSON/CSS/JS/fonts
- Browser caching: 1 year untuk font/gambar, 1 month untuk CSS/JS
- Security headers: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy
- www → non-www redirect: canonical domain consolidation
- Block dotfiles & sensitive paths:
.env,.git,/vendor,/node_modules,/storage/framework/cache/data/*.php - Health check:
/healthzwith access log suppression
3b. Supervisor Config (deploy/supervisor.conf)
Supervisor config sudah disediakan di deploy/supervisor.conf. Proses yang di-manage:
| Program | Queue | Procs | Sleep | Tries | Timeout |
|---|---|---|---|---|---|
crmoffice-scheduler |
— | 1 | — | — | — |
crmoffice-worker-default |
default | 3 | 3s | 3 | 60s |
crmoffice-worker-high |
high | 1 | 1s | 5 | 120s |
crmoffice-horizon |
— | 1 | — | — | — |
Semua program grouped di [group:crmoffice] — bisa start/stop/restart sekaligus:
supervisorctl start crmoffice:*
supervisorctl stop crmoffice:*
supervisorctl restart crmoffice:*
Log rotation via logrotate.d (included in supervisor.conf comments).
3c. Redis Setup
# Enable persistence (AOF)
redis-cli CONFIG SET appendonly yes
redis-cli CONFIG SET save "900 1 300 10 60 10000"
redis-cli CONFIG REWRITE
# Verify
redis-cli PING
redis-cli INFO persistence | grep -E "aof_enabled|rdb_last_save"
3d. Meilisearch Setup
# Install via apt (Ubuntu 22.04+) atau Docker
curl -L https://install.meilisearch.com | sh
sudo mv meilisearch /usr/local/bin/
sudo useradd -r -s /bin/false -m -d /var/lib/meilisearch meilisearch
# Create systemd service
cat > /etc/systemd/system/meilisearch.service <<'EOF'
[Unit]
Description=Meilisearch
After=network.target
[Service]
Type=simple
User=meilisearch
ExecStart=/usr/local/bin/meilisearch --db-path /var/lib/meilisearch/data --http-addr 127.0.0.1:7700 --master-key="${MEILISEARCH_KEY}"
Restart=always
Environment=MEILI_NO_ANALYTICS=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable --now meilisearch
# Index all models
cd /var/www/crmoffice && php artisan scout:import "App\Models\Customer"
php artisan scout:import "App\Models\Product"
# ... repeat for all searchable models
3e. SSL — Let's Encrypt via Certbot
# Install certbot
apt install -y certbot python3-certbot-nginx
# Obtain certificate (automatic Nginx config update)
certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Test auto-renewal
certbot renew --dry-run
# Certbot renews via systemd timer automatically
systemctl status certbot.timer
3f. Post-Deploy Commands
Setelah deploy, jalankan command berikut:
cd /var/www/crmoffice
# Cache all config/routes/views
php artisan optimize
# Cache Filament components
php artisan filament:cache-components
# Import scout indexes (if using Meilisearch)
php artisan scout:import "App\Models\Customer"
php artisan scout:import "App\Models\Product"
# Re-queue failed jobs (if any)
php artisan queue:retry all
# Verify health
curl -s http://yourdomain.com/healthz | python3 -m json.tool
# Expected: {"ok":true,"checks":{"mysql":true,"cache":true,"queue":true}}
# Warm the cache (optional — hit front page)
curl -s -o /dev/null http://yourdomain.com/
3g. .env.example Production Setup
File .env.example sudah lengkap dengan environment variables untuk production. Key sections:
- Redis:
REDIS_CLIENT=predis,REDIS_HOST=127.0.0.1, persistence on - Mail: SMTP via Mailgun/SES/MailerSend (
MAIL_MAILER=smtp) - Storage: S3-compatible (
AWS_*variables, defaultFILESYSTEM_DISK=local) - Search:
SCOUT_DRIVER=database(dev) orSCOUT_DRIVER=meilisearch(prod) - Sentry:
SENTRY_DSN=+SENTRY_TRACES_SAMPLE_RATE=0.1for error tracking - Spatie Media Library:
MEDIA_DISK=public - Sanctum:
SANCTUM_STATEFUL_DOMAINS=yourdomain.com+SESSION_DOMAIN=.yourdomain.com
Ganti
yourdomain.comdengan domain production Anda.
4. Production Hardening Checklist
-
APP_ENV=production,APP_DEBUG=false - Strong unique
APP_KEYper environment (never reuse across envs) - DB credentials least-privilege (no root)
- SSL enforced (Forge auto / certbot)
- HTTP → HTTPS redirect
- Cookie secure + samesite
- Rate limiting on
/api/*and/admin/login(Laravel defaults are good) - CSRF on web (Laravel default)
- Disable
.envaccess (Nginx blocks dotfiles) - Disable PHP errors display in production
- Sentry DSN configured for error tracking
- Daily mysqldump backup → S3 bucket
- Redis persistence (AOF) on
- Meilisearch master key set + IP-restricted
- Owner 2FA mandatory (Phase 7+)
- Provider API keys encrypted at rest (already done via Crypt::encryptString)
- Log shipping to Loki / CloudWatch / Papertrail
5. Backup Strategy
# Daily DB backup (add to /etc/cron.daily/)
DATE=$(date +%Y-%m-%d)
mysqldump --single-transaction --quick crmoffice | gzip > /var/backups/crmoffice-$DATE.sql.gz
aws s3 cp /var/backups/crmoffice-$DATE.sql.gz s3://crmoffice-backups/ # or rclone to R2/B2
find /var/backups -mtime +30 -delete
Test restore quarterly.
6. Monitoring
| Concern | Tool |
|---|---|
| Errors | Sentry (set SENTRY_DSN env) |
| Uptime | UptimeRobot / BetterStack — hit /healthz every minute |
| Queues | Horizon dashboard (if installed) |
| Slow queries | MySQL slow query log + pt-query-digest |
| Disk space | df -h cron alert |
/healthz endpoint returns 200 with {ok: true, checks: {mysql, cache, queue}} or 503 if any check fails.
7. Migration from Perfex CRM
Phase 9 deliverable. Shape:
php artisan migrate:from-perfex --source-host=... --source-db=... --map=payment
Reads Perfex tblpayment_modes, tblclients, tblinvoices, etc., maps to crmoffice schema.
8. Scale-out
Single server handles ~50 concurrent users / 100k records easily. Beyond that:
- Read replica DB (Laravel
mysql_readconnection) - Move Redis to managed (ElastiCache / Upstash)
- CDN for assets (Cloudflare free tier)
- Horizontal app servers behind LB (session sudah di Redis)
- Meilisearch dedicated instance
Multi-tenancy module (Phase 9) for serving many tenants from one install.