matheusmarnt / quick-deploy
A strategy-based deployment automation package for Laravel applications with Docker support
Requires
- php: ^8.2
- symfony/process: ^7.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0
- pestphp/pest: ^3.0
- phpstan/phpstan: ^2.0
This package is not auto-updated.
Last update: 2026-03-03 13:50:58 UTC
README
A strategy-based deployment automation package for Laravel applications with Docker support.
Requirements
- PHP 8.2+
- Docker & Docker Compose
- Composer
Installation
composer require matheusmarnt/quick-deploy
The QuickDeployServiceProvider is auto-discovered by Laravel.
Publishing Deployment Files
./vendor/bin/sail artisan quick-deploy:install
This copies Makefile, scripts, Docker configs, compose files and .env templates to your project root.
| Flag | Description |
|---|---|
--force |
Overwrite existing files without asking |
--scripts |
Install only the Makefile and scripts/ directory |
--docker |
Install only compose files and docker/ configs |
--env-files |
Install only .env templates |
Quick Start
php artisan quick-deploy:install make help # List all commands make deploy # Interactive deployment menu make deploy-dev # Deploy for development
Architecture
QuickDeploy uses the Strategy Pattern — the Deployer delegates to a DeploymentStrategy that encapsulates the full pipeline for each environment.
Deployer ──> DeploymentStrategy (interface)
▲
┌──────────┼──────────┐
│ │
DevelopmentDeployment ProductionDeployment
(Sail + MySQL) (Compose + MySQL + Redis + Nginx)
The service provider auto-detects the environment and wires the correct strategy:
- Development — uses
SailOrchestrator, health checks MySQL only, timeout 30s - Production — uses
DockerComposeOrchestrator, checks MySQL + Redis, timeout 60s
PHP API Usage
Basic Deploy
use Matheusmarnt\QuickDeploy\Config\DeploymentContext; use Matheusmarnt\QuickDeploy\Deployer; $deployer = app(Deployer::class); $context = new DeploymentContext( projectName: 'My App', projectSlug: 'my-app', workingDirectory: base_path(), ); $result = $deployer->deploy($context); if ($result->success) { echo "Deployed in {$result->totalDuration}s"; } else { echo "Failed: {$result->message}"; // Rollback is automatic on failure }
DeploymentContext — All Options
DeploymentContext is an immutable value object with all deploy configuration:
$context = new DeploymentContext( projectName: 'My App', // Human-readable name projectSlug: 'my-app', // URL-safe identifier (used in container names) workingDirectory: base_path(), // Absolute path to project root environment: 'production', // 'development' or 'production' composeFile: 'compose.production.yaml', // Docker Compose file name envTemplate: '.env.production.example', // Source template for .env ports: [ // Service port overrides 'app' => 8080, 'mysql' => 3307, ], envVariables: [ // Variables injected into .env 'APP_URL' => 'https://myapp.com', 'DB_PASSWORD' => 'secret', ], options: [ // Strategy-specific options 'healthCheckTimeout' => 120, // Production: seconds to wait for services ], );
Use with() to create a modified copy:
$staging = $context->with([ 'environment' => 'production', 'composeFile' => 'compose.staging.yaml', ]);
Validating Before Deploy
$errors = $deployer->validate($context); if (! empty($errors)) { foreach ($errors as $error) { echo "- {$error}\n"; } return; } $deployer->deploy($context);
Validation checks:
- Working directory exists
- Template file (
.env.example) exists - Production only: environment must be
'production'
Inspecting Deploy Results
DeploymentResult contains the outcome of each pipeline step:
$result = $deployer->deploy($context); // Overall result $result->success; // bool $result->strategy; // 'development' or 'production' $result->totalDuration; // float (seconds) $result->message; // Error reason on failure // Individual steps foreach ($result->steps as $step) { echo "{$step['step']}: " . ($step['success'] ? 'OK' : $step['message']); echo " ({$step['duration']}s)\n"; } // Only failed steps $result->failedSteps();
Switching Strategies
$deployer = app(Deployer::class); // Starts with auto-detected strategy // Switch to production $deployer->useStrategy('production'); // Or register a custom strategy $deployer->register(new StagingDeployment(/* ... */)); $deployer->useStrategy('staging'); // Check available strategies $deployer->availableStrategies(); // ['development', 'production', 'staging']
Explicit Rollback
Rollback is automatic on deploy failure, but you can trigger it manually:
$deployer->rollback($context); // Stops containers + restores .env from .env.backup
Health Checks
Health checkers are used internally during deploy, but can also be used directly:
use Matheusmarnt\QuickDeploy\Contracts\HealthChecker; $checker = app(HealthChecker::class); $result = $checker->check(base_path(), timeoutSeconds: 30); if ($result->healthy) { echo "{$result->service} is up ({$result->responseTimeMs}ms, {$result->attemptsUsed} attempts)"; } else { echo "{$result->service} failed: {$result->message}"; }
Environment Configuration
Read, write and validate .env files programmatically:
use Matheusmarnt\QuickDeploy\Contracts\EnvironmentConfigurator; $env = app(EnvironmentConfigurator::class); // Create .env from template with variable overrides $env->configure( templatePath: base_path('.env.example'), targetPath: base_path('.env'), variables: ['APP_URL' => 'https://myapp.com'], ); // Read/write individual values $env->set(base_path('.env'), 'APP_DEBUG', 'false'); $value = $env->get(base_path('.env'), 'APP_URL'); // 'https://myapp.com' // Check required keys exist and have values $missing = $env->validateRequired(base_path('.env'), [ 'APP_KEY', 'APP_URL', 'DB_PASSWORD', ]); // Returns: ['APP_KEY'] if APP_KEY is empty
Port Management
Check port availability before deployment:
use Matheusmarnt\QuickDeploy\Environment\PortManager; use Matheusmarnt\QuickDeploy\Support\ProcessRunner; $portManager = new PortManager(app(ProcessRunner::class)); // Check a port $portManager->isPortInUse(3306); // true/false // Find conflicts in a set of ports $conflicts = $portManager->findConflicts([ 'app' => 80, 'mysql' => 3306, ]); // Returns: ['mysql' => 3306] if 3306 is in use // Find an available port $port = $portManager->suggestAlternative(8080); // 8081, 8082, etc.
Container Orchestration
Execute Docker commands through the orchestrator abstraction:
use Matheusmarnt\QuickDeploy\Contracts\ContainerOrchestrator; $docker = app(ContainerOrchestrator::class); $dir = base_path(); $compose = 'compose.yaml'; $docker->build($dir, $compose); // docker compose build --no-cache $docker->up($dir, $compose); // docker compose up -d $docker->down($dir, $compose); // docker compose down $docker->restart($dir, $compose); // docker compose restart // Run a command inside a container $output = $docker->run($dir, $compose, 'app', [ 'php', 'artisan', 'migrate', '--force', ]);
The orchestrator auto-selects SailOrchestrator (dev) or DockerComposeOrchestrator (prod) based on the presence of vendor/bin/sail.
Creating a Custom Strategy
Implement DeploymentStrategy for custom environments:
use Matheusmarnt\QuickDeploy\Contracts\DeploymentStrategy; use Matheusmarnt\QuickDeploy\Config\DeploymentContext; use Matheusmarnt\QuickDeploy\Result\DeploymentResult; final class StagingDeployment implements DeploymentStrategy { public function __construct( private readonly ContainerOrchestrator $orchestrator, private readonly EnvironmentConfigurator $envConfigurator, private readonly HealthChecker $healthChecker, ) {} public function name(): string { return 'staging'; } public function validate(DeploymentContext $context): array { $errors = []; if (! is_dir($context->workingDirectory)) { $errors[] = 'Working directory does not exist.'; } return $errors; } public function deploy(DeploymentContext $context): DeploymentResult { $start = microtime(true); $steps = []; // 1. Setup environment $this->envConfigurator->configure( $context->templateFilePath(), $context->envFilePath(), $context->envVariables, ); $steps[] = ['step' => 'Environment Setup', 'success' => true, 'message' => '', 'duration' => 0.0]; // 2. Build and start $this->orchestrator->build($context->workingDirectory, $context->composeFile); $this->orchestrator->up($context->workingDirectory, $context->composeFile); // 3. Health check $health = $this->healthChecker->check($context->workingDirectory); if (! $health->healthy) { return DeploymentResult::failed('staging', $steps, microtime(true) - $start, $health->message); } return DeploymentResult::succeeded('staging', $steps, microtime(true) - $start); } public function rollback(DeploymentContext $context): void { $this->orchestrator->down($context->workingDirectory, $context->composeFile); } }
Register it:
$deployer = app(Deployer::class); $deployer->register(new StagingDeployment( app(ContainerOrchestrator::class), app(EnvironmentConfigurator::class), app(HealthChecker::class), )); $deployer->useStrategy('staging'); $deployer->deploy($context);
Deploy Pipeline
Development
| Step | Action |
|---|---|
| 1. Environment | Creates .env from template, backs up existing |
| 2. Build | sail build --no-cache |
| 3. Start | sail up -d |
| 4. Health | MySQL ping (30s timeout) |
| 5. Migrate | php artisan migrate --seed --force |
| 6. Optimize | php artisan optimize:clear |
Production
| Step | Action |
|---|---|
| 1. Environment | Creates .env, forces APP_ENV=production and APP_DEBUG=false |
| 2. Validate | Checks required keys: APP_KEY, APP_URL, DB_* |
| 3. Stop | docker compose down (clean slate) |
| 4. Build | docker compose build --no-cache |
| 5. Start | docker compose up -d |
| 6. Health | MySQL + Redis ping (60s timeout) |
| 7. Migrate | php artisan migrate --force (no seed) |
| 8. Optimize | php artisan optimize (cache all) |
On failure at any step, rollback runs automatically: stops containers and restores .env from backup.
Available Make Commands
| Command | Description |
|---|---|
make deploy |
Interactive deployment menu |
make deploy-dev |
Development deployment |
make deploy-prod |
Production deployment |
make up / make down |
Start / stop containers |
make logs |
Follow container logs |
make status / make health |
Container status / health check |
make restart / make clean |
Restart / remove everything |
make migrate |
Run migrations |
make migrate-fresh |
Drop and re-run migrations |
make migrate-seed |
Migrations with seeding |
make shell |
App container shell |
make mysql-shell / make redis-cli |
Database shells |
make composer / make npi / make nrb |
Dependency management |
make optimize / make backup / make pull |
Maintenance |
Quality Scripts
composer test # Run Pest tests composer test:coverage # Run tests with coverage composer analyze # Run PHPStan (level max) composer format # Fix code style with Pint composer check # Run all checks
Contributing
- Fork the repository.
- Create a feature branch:
git checkout -b feature/my-feature. - Write tests for your changes.
- Run
composer checkto ensure quality. - Submit a pull request.
License
MIT. See LICENSE for details.