cuonggt/bosun
Composer 安装命令:
composer require cuonggt/bosun
包简介
Provision servers and deploy Laravel applications with zero downtime, straight from artisan.
README 文档
README
A bosun (boatswain) is the crew member who provisions, maintains, and readies the ship before it sails. This package does the same for your Laravel app.
Provision servers and deploy Laravel applications with zero downtime — straight from artisan.
This package gives you two commands:
| Command | What it does |
|---|---|
php artisan setup |
Provisions a fresh Ubuntu server: PHP-FPM, Nginx, Composer, Node, Redis, Supervisor, Certbot, a firewall and a non-root deploy user. |
php artisan deploy |
Deploys your app with zero downtime using timestamped releases, shared .env/storage, atomic symlink swaps and automatic rollback-ready release pruning. |
It talks to your servers over SSH using phpseclib — there's no dependency on a local ssh binary, and the whole thing is unit-tested against a fake connection.
Requirements
- PHP 8.1+
- Laravel 10, 11, 12 or 13
- A target server running Ubuntu 22.04 or 24.04 that you can reach over SSH as
root(most cloud providers give you this on a fresh box).setuprefuses to run on anything else.
Installation
composer require cuonggt/bosun
The service provider is auto-discovered. Publish the config file:
php artisan vendor:publish --tag=bosun-config
This creates config/bosun.php.
Configuration
Everything is driven by config/bosun.php, which reads from your .env. A minimal setup:
DEPLOY_HOST=203.0.113.10 DEPLOY_USER=deployer DEPLOY_KEY=~/.ssh/id_rsa DEPLOY_DOMAIN=example.com DEPLOY_REPOSITORY=git@github.com:acme/app.git DEPLOY_BRANCH=main DEPLOY_PATH=/home/deployer/app
Each server is an entry under servers in the config file, so you can define production, staging, and more. Key options:
| Option | Description | Default |
|---|---|---|
host / port |
Where to connect. | — / 22 |
username |
The deploy user — created during setup, connected as during deploy. |
deployer |
key / passphrase / password |
Auth. A readable key file wins; otherwise the password is used. | ~/.ssh/id_rsa |
domain |
Server name for the generated Nginx site. | — |
deploy_path |
Where the app lives on the server. {application} is substituted. |
/home/deployer/{application} |
php / node |
Stack to provision. | 8.3 / 20 |
App-wide options (outside servers): repository, branch, database, cloudflare, shared_files, shared_dirs, keep_releases, build_assets, queue, and hooks. Set cloudflare to true if the site sits behind Cloudflare, so Nginx logs the real visitor IP.
Provisioning a server
Point the package at a fresh server and run:
php artisan setup production
By default it connects as root (override with --user). It will:
- Update and upgrade apt, then install base tooling (a build toolchain for native npm/pecl modules, git,
jq,cron, etc.). - Harden SSH to key-only authentication (password login is disabled).
- Install PHP (with the common Laravel extensions, including the MySQL/Postgres/SQLite PDO drivers), Composer, Nginx, Redis, Supervisor, Node and Certbot.
- Create the unprivileged deploy user, authorizing the same SSH key you connected with (add another with
--key). - Generate an SSH deploy key for that user and trust your Git host, so it can clone a private repo (see below).
- Grant that user passwordless permission to reload PHP-FPM/Nginx and control Supervisor — nothing more.
- Configure the firewall (UFW: SSH + HTTP/HTTPS), install fail2ban, and enable automatic security updates.
- Lay out the deploy directory, write the Nginx site and the Supervisor queue worker, then start everything. When a domain is set, a catch-all server rejects requests that don't match it (so the app never answers on the bare IP or a spoofed
Host).
Every step is idempotent, so re-running setup to add an extension or change a setting is safe.
Private repositories
Use an SSH repository URL (git@github.com:you/app.git). At the end of setup, bosun prints the deploy user's public key:
Add this read-only deploy key to your Git repository, then deploy:
ssh-ed25519 AAAA… bosun-app@203.0.113.10
Register it as a read-only deploy key (GitHub: Settings → Deploy keys; GitLab: Settings → Repository → Deploy keys), then php artisan deploy. The key is per-server, read-only, and regenerated only if absent — so re-running setup never invalidates a key you've already registered.
Databases are out of scope — with one exception. bosun installs the MySQL/Postgres/SQLite PDO drivers but does not provision a database server; point your app at a managed or external database via
DB_*in the server'sshared/.env. For SQLite, setdatabaseto"sqlite"(orDEPLOY_DATABASE=sqlite) and bosun keepsdatabase/database.sqliteinshared/— symlinked into each release and writable by the web server — so it persists across deploys.
php artisan setup production --user=root --key=~/.ssh/deploy_key.pub
Deploying
php artisan deploy production
This performs a zero-downtime deploy:
/home/deployer/app/
├── current -> releases/20260627T120000 # atomic symlink
├── releases/
│ ├── 20260627T120000/
│ └── … # previous releases kept for rollback
└── shared/
├── .env # persists across deploys
└── storage/ # persists across deploys
The sequence:
- Clone the repo into a fresh, timestamped release and record the commit in a
REVISIONfile. - Symlink shared files/dirs (
.env,storage) into the release. composer install --no-dev, then (unless--no-build)npm ci && npm run build.storage:link, cache config/routes/views/events, and runphp artisan migrate --force.- Atomically swap the
currentsymlink to the new release. - Reload PHP-FPM (to refresh OPcache) and restart queue workers.
- Prune old releases beyond
keep_releases.
Because the symlink is swapped only after the release is fully built and migrated, no request ever hits a half-deployed app.
First deploy
On the very first deploy there's no .env yet, so the package seeds shared/.env from the first of these that exists in your repo: .env.<environment> (the environment is the server name, e.g. .env.production), then .env, then .env.example. Commit a .env.production (in your private repo) with the real APP_KEY, DB creds, etc. and the app is configured from the first deploy — otherwise bosun seeds a template, skips migrations and queue:restart (both need a configured, migrated app), and reminds you to fill it in on the server:
ssh deployer@203.0.113.10 nano /home/deployer/app/shared/.env # set APP_KEY, DB creds, etc. exit php artisan deploy production # this run will migrate
Useful options
php artisan deploy staging # deploy to a different server php artisan deploy production --branch=hotfix php artisan deploy production --no-build # skip front-end asset build php artisan deploy production -v # stream live command output
-v also works on setup, streaming the raw server output for each step.
Deployment hooks
Run extra commands on the server during a deploy via config/bosun.php:
'hooks' => [ 'before' => ['php artisan down'], // runs in the deploy path, before building 'after' => ['php artisan up', 'php artisan horizon:terminate'], // runs in the new release ],
How it fits together
SetupCommand ─┐ ┌─ Provisioner ──┐
├─ RemoteCommand (rendering)┤ ├─ RemoteScript ─ Connection (SSH)
DeployCommand ┘ └─ DeploymentRunner ┘
Connectionis a tiny interface (run,put,disconnect). The real one uses phpseclib; tests use an in-memory fake.RemoteScriptorchestrates a sequence of tasks but knows nothing about the console.RemoteCommandrenders those tasks (compact ticks, or streamed output with-v) and reports failures.
That separation is why the provisioning and deployment logic is fully unit-tested without ever opening a socket.
Testing
composer install
composer test
Security notes
- The deploy user is unprivileged. Its only
sudorights are passwordless reloads of PHP-FPM/Nginx and Supervisor control, written to/etc/sudoers.dand validated withvisudo -c. - SSH password authentication is disabled — key-only login. (So provision with a key; a password-only server can't be deployed to.)
- fail2ban is installed to throttle SSH brute-force attempts, and automatic security updates (
unattended-upgrades) are enabled. - Provisioning enables UFW and opens only SSH and HTTP/HTTPS. Nginx hides its version (
server_tokens off), and with a domain set a catch-all server rejects requests for any other host. - For HTTPS, run
sudo certbot --nginx -d example.comon the server after the first deploy (Certbot is already installed).
License
MIT — see LICENSE.md.
统计信息
- 总下载量: 1
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 9
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-09