veltix/zdt

Zero Downtime Deployment Tool - Deploy your applications safely with GitHub Actions

Fund package maintenance!
Buy Me A Coffee

Installs: 23

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

Type:project

pkg:composer/veltix/zdt

v1.1.7 2025-12-02 22:37 UTC

This package is auto-updated.

Last update: 2025-12-02 22:51:05 UTC


README

ZDT - Zero Downtime Deployment Tool

Tests Latest Version PHP Version License

A powerful, zero-downtime deployment tool built with Laravel Zero. Deploy your applications safely with automatic rollbacks, health checks, and comprehensive release management.

Features

  • Zero Downtime Deployments - Deploy without affecting live traffic using symlink switching
  • Atomic Deployments - All-or-nothing deployments with automatic rollback on failure
  • Release Management - Keep multiple releases with automatic cleanup
  • SSH Key Authentication - Secure deployments using SSH keys
  • Deployment Hooks - Run custom scripts at any stage of deployment
  • Health Checks - Validate deployments with HTTP health checks
  • Rollback Support - Instantly rollback to any previous release
  • Composer & NPM Support - Automatically install dependencies
  • Database Migrations - Run migrations safely during deployment
  • Cross-Platform - Works on Linux, macOS, and Windows

Installation

No installation required! ZDT is automatically installed in your GitHub Actions workflow.

Quick Start

1. Set Up GitHub Secrets

In your GitHub repository, go to Settings → Secrets and variables → Actions and add:

  • DEPLOY_HOST - Your server hostname (e.g., your-server.com)
  • DEPLOY_USERNAME - SSH username (e.g., deployer)
  • DEPLOY_SSH_KEY - Your private SSH key for authentication
  • DEPLOY_PATH - Deployment path on server (e.g., /var/www/your-app)

2. Create Deployment Workflow

Create .github/workflows/deploy.yml in your repository:

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Install ZDT
        run: composer global require veltix/zdt

      - name: Deploy to Server
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }}
          DEPLOY_KEY_PATH: ${{ secrets.DEPLOY_SSH_KEY }}
          DEPLOY_REPO_URL: git@github.com:${{ github.repository }}.git
          DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
          DEPLOY_BRANCH: ${{ github.ref_name }}
          DEPLOY_HOOKS_AFTER_CLONE: |
            composer install --no-dev --optimize-autoloader
          DEPLOY_HOOKS_BEFORE_ACTIVATE: |
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
          DEPLOY_HOOKS_AFTER_ACTIVATE: |
            php artisan queue:restart
        run: |
          echo "$DEPLOY_KEY_PATH" > /tmp/deploy_key
          chmod 600 /tmp/deploy_key
          export DEPLOY_KEY_PATH=/tmp/deploy_key
          php zdt deploy

3. Deploy

Push to your main branch and deployment happens automatically! Your application is deployed with zero downtime.

How It Works

When you run zdt deploy in your workflow, it:

  1. Connects to your server via SSH
  2. Clones your repository to a new release directory
  3. Installs dependencies (Composer/NPM)
  4. Runs your deployment hooks
  5. Executes database migrations
  6. Switches the symlink to the new release atomically
  7. Performs health checks
  8. Cleans up old releases
  9. Automatically rolls back if anything fails

Available Commands

Primary Commands

deploy - Deploy your application

- run: php zdt deploy

rollback - Rollback to previous release

- run: php zdt rollback
- run: php zdt rollback --release=20250129143000  # Specific release

list-releases - View all releases on server

- run: php zdt list-releases

cleanup - Remove old releases

- run: php zdt cleanup --keep=3  # Keep 3 most recent releases

The --keep parameter controls how many releases to retain (default: 5). You can also configure this in your workflow:

# Cleanup as part of deployment workflow
- name: Cleanup Old Releases
  run: |
    echo "$DEPLOY_KEY_PATH" > /tmp/deploy_key
    chmod 600 /tmp/deploy_key
    export DEPLOY_KEY_PATH=/tmp/deploy_key
    php zdt cleanup --keep=3
  env:
    DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
    DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }}
    DEPLOY_KEY_PATH: ${{ secrets.DEPLOY_SSH_KEY }}
    DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}

Configuration

Environment Variables

Configure deployment using GitHub Secrets:

# Required GitHub Secrets
DEPLOY_HOST=your-server.com
DEPLOY_USERNAME=deployer
DEPLOY_SSH_KEY=<your-ssh-private-key>
DEPLOY_PATH=/var/www/your-app

Automatically Detected:

  • DEPLOY_REPO_URL - Set from ${{ github.repository }}
  • DEPLOY_BRANCH - Set from ${{ github.ref_name }}

Optional Environment Variables:

DEPLOY_PORT=22
DEPLOY_TIMEOUT=300
DEPLOY_KEEP_RELEASES=5  # Number of releases to keep (default: 5)

Note: The --keep flag on commands will override the DEPLOY_KEEP_RELEASES environment variable.

Deployment Hooks

Hooks allow you to run custom commands at different stages:

  • before_clone - Before cloning the repository
  • after_clone - After cloning, before dependencies
  • before_activate - Before switching symlink
  • after_activate - After successful deployment
  • after_rollback - After rollback completes

Use environment variables with YAML multiline syntax in your workflow:

env:
  DEPLOY_HOOKS_BEFORE_CLONE: |
    echo "Starting deployment"
  DEPLOY_HOOKS_AFTER_CLONE: |
    composer install --no-dev --optimize-autoloader
  DEPLOY_HOOKS_BEFORE_ACTIVATE: |
    php artisan migrate --force
    php artisan config:cache
    php artisan route:cache
  DEPLOY_HOOKS_AFTER_ACTIVATE: |
    php artisan queue:restart
  DEPLOY_HOOKS_AFTER_ROLLBACK: |
    php artisan cache:clear

Important: Hook commands that fail will abort the deployment and trigger automatic rollback.

Custom Shared Paths

Share additional files or directories across releases by symlinking them from the shared/ directory.

In deploy.config.php:

'shared_paths' => [
    'resources/lang' => 'lang',           // Translations
    'public/uploads' => 'uploads',        // User uploads
    'config/custom.php' => 'custom.php',  // Custom config file
],

In GitHub Actions:

Use the DEPLOY_HOOKS_AFTER_CLONE hook to create symlinks:

env:
  DEPLOY_HOOKS_AFTER_CLONE: |
    composer install --no-dev --optimize-autoloader
    ln -sf $DEPLOY_PATH/shared/lang resources/lang
    ln -sf $DEPLOY_PATH/shared/uploads public/uploads

What gets shared by default:

  • .env - Copied to each release
  • storage/ - Symlinked from shared/storage

What you can add:

  • Translations (resources/lang)
  • User uploads (public/uploads)
  • Custom config files
  • Cache directories
  • Any file or directory that should persist across deployments

Server Directory Structure

ZDT creates the following structure on your deployment server:

/var/www/your-app/
├── releases/
│   ├── 20250129143000/
│   │   ├── .env                      ← Copied from shared
│   │   ├── storage/                  → Symlink to shared/storage
│   │   └── resources/lang/           → Symlink to shared/lang (if configured)
│   ├── 20250129145000/
│   └── 20250129150000/
├── shared/
│   ├── .env                          ← Master environment file
│   ├── storage/                      ← Shared storage (logs, uploads, cache)
│   └── lang/                         ← Custom shared path (if configured)
└── current → releases/20250129150000
  • releases/ - Timestamped release directories
  • shared/ - Persistent files shared across all releases
  • current - Symlink pointing to the active release

GitHub Actions Workflows

Deploy on Push to Main

Automatically deploy when code is pushed to the main branch:

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - name: Install ZDT
        run: composer global require veltix/zdt
      - name: Deploy
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }}
          DEPLOY_KEY_PATH: ${{ secrets.DEPLOY_SSH_KEY }}
          DEPLOY_REPO_URL: git@github.com:${{ github.repository }}.git
          DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
          DEPLOY_BRANCH: ${{ github.ref_name }}
          DEPLOY_HOOKS_AFTER_CLONE: |
            composer install --no-dev --optimize-autoloader
          DEPLOY_HOOKS_BEFORE_ACTIVATE: |
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
          DEPLOY_HOOKS_AFTER_ACTIVATE: |
            php artisan queue:restart
        run: |
          echo "$DEPLOY_KEY_PATH" > /tmp/deploy_key
          chmod 600 /tmp/deploy_key
          export DEPLOY_KEY_PATH=/tmp/deploy_key
          php zdt deploy

Manual Deployment with workflow_dispatch

Deploy manually from GitHub Actions tab:

name: Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        default: 'production'
        type: choice
        options:
          - production
          - staging

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - name: Install ZDT
        run: composer global require veltix/zdt
      - name: Deploy to ${{ inputs.environment }}
        env:
          DEPLOY_HOST: ${{ secrets[format('DEPLOY_HOST_{0}', inputs.environment)] }}
          DEPLOY_USERNAME: ${{ secrets[format('DEPLOY_USERNAME_{0}', inputs.environment)] }}
          DEPLOY_KEY_PATH: ${{ secrets[format('DEPLOY_SSH_KEY_{0}', inputs.environment)] }}
          DEPLOY_REPO_URL: git@github.com:${{ github.repository }}.git
          DEPLOY_PATH: ${{ secrets[format('DEPLOY_PATH_{0}', inputs.environment)] }}
          DEPLOY_BRANCH: ${{ github.ref_name }}
          DEPLOY_HOOKS_AFTER_CLONE: |
            composer install --no-dev --optimize-autoloader
          DEPLOY_HOOKS_BEFORE_ACTIVATE: |
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
          DEPLOY_HOOKS_AFTER_ACTIVATE: |
            php artisan queue:restart
        run: |
          echo "$DEPLOY_KEY_PATH" > /tmp/deploy_key
          chmod 600 /tmp/deploy_key
          export DEPLOY_KEY_PATH=/tmp/deploy_key
          php zdt deploy

Deploy with Tests

Run tests before deploying:

name: Test and Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - run: composer install
      - run: vendor/bin/pint --test
      - run: php zdt test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - name: Install ZDT
        run: composer global require veltix/zdt
      - name: Deploy
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_USERNAME: ${{ secrets.DEPLOY_USERNAME }}
          DEPLOY_KEY_PATH: ${{ secrets.DEPLOY_SSH_KEY }}
          DEPLOY_REPO_URL: git@github.com:${{ github.repository }}.git
          DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
          DEPLOY_BRANCH: ${{ github.ref_name }}
          DEPLOY_HOOKS_AFTER_CLONE: |
            composer install --no-dev --optimize-autoloader
          DEPLOY_HOOKS_BEFORE_ACTIVATE: |
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
          DEPLOY_HOOKS_AFTER_ACTIVATE: |
            php artisan queue:restart
        run: |
          echo "$DEPLOY_KEY_PATH" > /tmp/deploy_key
          chmod 600 /tmp/deploy_key
          export DEPLOY_KEY_PATH=/tmp/deploy_key
          php zdt deploy

Advanced Workflow Example

For complex production deployments, see the complete example:

examples/advanced-deploy.yml

This example demonstrates:

  • Multiple deployment environments (production, staging, development)
  • Pre-deployment testing with skip option
  • Advanced deployment hooks
  • Automatic rollback on failure
  • Post-deployment health checks
  • Slack notifications for success/failure
  • Production deployment tagging
  • Environment-specific GitHub Secrets

Server Requirements

Your deployment server needs:

  • SSH access with key-based authentication
  • Git
  • PHP 8.2 or higher (if deploying PHP applications)
  • Composer (if using PHP dependencies)
  • Node.js & NPM (if building frontend assets)

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/configuration-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (php zdt test)
  5. Ensure code style is correct (vendor/bin/pint)
  6. Commit your changes (git commit -m 'feat: flexible configuration feature')
  7. Push to the branch (git push origin feature/configuration-feature)
  8. Open a Pull Request

Security

If you discover any security issues, please email security@veltix.sh instead of using the issue tracker.

Credits

Built with Laravel Zero - A micro-framework for building console applications.

License

ZDT is open-source software licensed under the MIT license.

Made with ❤️ using Laravel Zero