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
Requires
- php: ^8.2
- laravel-zero/framework: ^12.0.2
- phpseclib/phpseclib: ^3.0
Requires (Dev)
- laravel/pint: ^1.25.1
- mockery/mockery: ^1.6.12
- pestphp/pest: ^3.8.4|^4.1.2
- rector/rector: ^2.2
README
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 authenticationDEPLOY_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:
- Connects to your server via SSH
- Clones your repository to a new release directory
- Installs dependencies (Composer/NPM)
- Runs your deployment hooks
- Executes database migrations
- Switches the symlink to the new release atomically
- Performs health checks
- Cleans up old releases
- 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 fromshared/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:
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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/configuration-feature) - Write tests for your changes
- Ensure all tests pass (
php zdt test) - Ensure code style is correct (
vendor/bin/pint) - Commit your changes (
git commit -m 'feat: flexible configuration feature') - Push to the branch (
git push origin feature/configuration-feature) - 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