matheusmarnt/quick-deploy

A strategy-based deployment automation package for Laravel applications with Docker support

Maintainers

Package info

github.com/matheusmarnt/quick-deploy

pkg:composer/matheusmarnt/quick-deploy

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-03-02 20:42 UTC

README

QuickDeploy Logo

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

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/my-feature.
  3. Write tests for your changes.
  4. Run composer check to ensure quality.
  5. Submit a pull request.

License

MIT. See LICENSE for details.