rebelinblue/deployer
Rebelinblue Deployer is a Laravel package for simple, zero-downtime deployments. Configure servers and release steps, publish atomic releases, and roll back safely using Artisan commands—without complex deployment tooling.
Read this first: it's an application, not a package. rebelinblue/deployer is a self-hosted Laravel app (an Envoyer-style deployment manager), not something you composer require into an existing project. You run it as its own service, point it at your repos, and it SSHes into your servers to deploy them. That reframing determines everything below.
Minimal setup:
deploy.example.com), composer install, copy .env.example → .env, php artisan key:generate.composer.json for the PHP constraint before you start — historically this has been a Laravel 5.x-era app. Don't assume from old notes: 0.0.47 is still shipping fixes, so read the actual constraint and test booting on your target PHP.php artisan migrate, create the storage symlink, build assets (the Node toolchain is old — expect to fight node-sass/webpack if you rebuild assets; many people just serve the committed build).Where to look in the codebase when you need to change behaviour:
routes/ — the webhook endpoints and the project/environment CRUD are the two things you'll touch most.app/Jobs/ (or equivalent) — the job that actually runs a deployment; this is the heart of the app.app/Http/Controllers/ — project, environment, deployment, and heartbeat controllers.config/ — notification channels, queue, and any app-specific settings.database/migrations/ — the schema tells you the real domain model faster than the UI does.First use case: get one environment deploying manually from the UI before you wire up webhooks. A manual deploy with three steps (git clone/checkout, composer install --no-dev -o, symlink switch) proves SSH, paths, and permissions all work. Only then add the webhook.
The domain model is the workflow. Project → Environment → Server(s) → Deployment → Steps. A deployment is an ordered list of shell commands executed over SSH against the target server(s). Internalise this and the UI stops feeling arbitrary: everything you configure is either "where do I run it" (servers/environment) or "what do I run" (steps).
Use the releases + current symlink pattern. The idiomatic setup (borrowed from Capistrano) is: deploy into releases/<timestamp>, then atomically repoint a current symlink. Do it this way even though the tool allows you to deploy in place — it gives you near-instant rollback (repoint the symlink to the previous release) and avoids half-broken docroots. Structure your steps as:
composer install --no-dev --optimize-autoloader).php artisan migrate --force).config:cache, route:cache, view:cache) — in that order, after env vars are in place.current symlink.php artisan queue:restart so long-running workers pick up new code.Lean on shared folders for anything that must survive a deploy. .env, storage/, uploads, and logs belong in the shared area, symlinked into each release. This is exactly the feature 0.0.47 tightened up: symlinks are now created with full paths, which removes a whole class of "worked from the app, broke under cron/fpm" relative-path bugs. Keep your shared-folder list explicit and small, and audit it during the upgrade since the structure is being rebuilt once.
Treat every step as idempotent and non-interactive. A step that prompts (Composer asking for a token, an ssh host-key confirmation, npm asking to install) will hang the deployment until it times out. Set COMPOSER_AUTH/COMPOSER_HOME, pre-seed known_hosts, use -n/--no-interaction everywhere. Because a failed step leaves earlier steps applied with no automatic rollback, steps must be safe to re-run.
Put secrets in environment variables, not in step commands. The step text is stored in the database and rendered in the UI — anyone with dashboard access sees it. Keep DB passwords, API keys, and .env contents in the environment's variable configuration, and let steps reference the generated file. Generate the app's .env from those variables as an early step, ideally writing it into the shared folder so it persists across releases.
Prefer absolute, deploy-owned paths; tildes now work but stay explicit. 0.0.47 fixed ~ expansion, so ~/app/current no longer misbehaves. Still write /home/deploy/app/... in steps: absolute paths are unambiguous in logs, survive sudo steps (where ~ may resolve to root), and match the new full-path symlink behaviour. If you previously worked around the tilde bug with hardcoded /home/<user> strings, that workaround is now cruft — clean it up.
Wire webhooks per project, and filter by branch. GitHub/GitLab/Bitbucket payload URLs plus a secret are the intended automation path. Always set the secret — an unauthenticated deploy endpoint is remote code execution by proxy. Confirm the branch filter actually matches your branch naming (main vs master catches everyone once), and remember the Deployer host must be able to reach the payload and (for clone-from-origin setups) the repo.
Fan out notifications at the environment level. Slack/email/webhook channels per project or environment mean the team sees failures where they already are. Treat a failing deploy notification as a paging signal — it usually precedes a real outage if your symlink switch is late in the step list. Since 0.0.47 logs stderr from processes that exited non-zero, the notification now links to an actually useful log rather than a silent failure.
Use heartbeats for cron. The heartbeat feature is an underrated part of this app: cron jobs curl a URL, and the dashboard alerts you when a job stops reporting. Cheap, effective uptime monitoring for scheduled work — set it up alongside your deployments.
Run it as a dedicated deploy user. A non-root deploy account with a passphrase-less key and narrowly scoped sudo for reload commands (php-fpm, nginx) keeps the blast radius small and makes the SSH permission model obvious when something fails.
.env, storage/, uploads, and logs all landed in the new structure before rolling it across everything.composer.json, pin your PHP version in the vhost, commit composer.lock, and keep the dashboard behind auth. If you're starting fresh, still compare against deployer/deployer, Envoyer, Ploi, or plain CI/CD — adopting this means you own it.queue:work is running and check failed_jobs before debugging anything else.cds into current and dereferences storage works, but hand-written scripts assuming relative symlink targets may not. Grep your step list and any custom scripts for relative symlink assumptions.storage:link and any writable directories (storage/, bootstrap/cache/) need the correct owner for the web user, not the deploy user. Do this as an explicit step against the shared folder, not the release, or you'll get intermittent 500s after an otherwise "successful" deploy.config:cache before env changes is a classic footgun. Caching config then mutating .env means the app silently uses stale values. Order steps: write env → clear → cache.chmod 600), wrong deploy path, non-interactive hangs. Keep a manual ssh deploy@host 'echo ok' as your first diagnostic.How can I help you explore Laravel packages today?