Skip to content

About

Signed ZIP deployments to shared PHP hosting over FTPS

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

FTPS ZIP Deploy

Deploy a prepared PHP application to shared hosting as one authenticated ZIP release instead of thousands of individual FTP operations.

The action is designed for hosting environments with FTPS and web PHP but without SSH. It uploads one release archive, an optional environment file, a short standalone PHP bootstrap, and a release-specific HMAC secret. The bootstrap validates the complete request before extracting or changing application files.

Requirements

On the GitHub runner:

  • PHP 8.2 or newer with curl and zip
  • a production-ready source directory; dependency installation and frontend builds remain the caller's responsibility

On the server:

  • PHP 8.2 or newer with zip
  • an HTTPS application URL
  • a web document root below the application root, commonly public/
  • write access for PHP to the application root
  • explicit FTPS with a verifiable TLS certificate

Usage

Build the application first, then pass the prepared directory to the action. Keep deployment secrets in a protected GitHub Environment and do not expose them to pull-request workflows.

name: Deploy

on:
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: production
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

      - name: Build production release
        run: |
          composer install --no-dev --optimize-autoloader
          npm ci
          npm run build
          mkdir -p "$RUNNER_TEMP/release"
          rsync -a --exclude='.git/' --exclude='.github/' --exclude='.env*' ./ "$RUNNER_TEMP/release/"

      - name: Deploy
        uses: tbuck-software/ftps-zip-deploy@FULL_COMMIT_SHA
        with:
          source-directory: ${{ runner.temp }}/release
          ftp-server: ${{ secrets.FTP_SERVER }}
          ftp-username: ${{ secrets.FTP_USERNAME }}
          ftp-password: ${{ secrets.FTP_PASSWORD }}
          application-url: https://example.com
          deployment-secret: ${{ secrets.DEPLOYMENT_SECRET }}
          environment-file: .env.production
          post-deploy-hook: deploy/post-release.php
          health-url: https://example.com/up

Generate the deployment secret with at least 32 random bytes, for example:

openssl rand -hex 32

Pin this action to a reviewed full commit SHA. A movable tag such as @v1 is convenient but does not provide the same supply-chain guarantee.

Post-deploy hooks

The optional hook is a PHP file inside the prepared release. It must return a callable. The server executes it only after authentication, archive verification, safe extraction, and file installation.

<?php

return static function (array $deployment): int {
    require $deployment['root'].'/vendor/autoload.php';
    $app = require $deployment['root'].'/bootstrap/app.php';
    $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

    return Illuminate\Support\Facades\Artisan::call('migrate', ['--force' => true]);
};

Hooks are trusted release code. They must be deterministic, non-interactive, idempotent where practical, and safe to retry. Database migrations should remain backward compatible because filesystem rollback cannot reverse a committed database migration.

Server layout

All private state owned by the action lives under one directory outside the document root:

.ftps-zip-deploy/
├── incoming/       # release-specific ZIP, environment, and secret files
├── staging/        # verified extraction workspace
├── backups/        # rollback data during a deployment
├── state/
│   └── manifest.json
├── deploy.lock
└── secret

The only public component is the authenticated bootstrap at:

public/.well-known/ftps-zip-deploy/index.php

The .well-known location avoids loose deployment scripts in public/ while remaining compatible with web servers that deny arbitrary dot paths.

Inputs

Input Required Default Purpose
source-directory no . Prepared release tree
ftp-server yes — FTPS host and optional port
ftp-username yes — FTPS login
ftp-password yes — FTPS password
server-directory no / Application root relative to the FTP account
application-url yes — HTTPS application base URL
deployment-secret yes — HMAC secret, at least 32 characters
environment-file no empty Environment file uploaded outside the ZIP
environment-target no .env Private relative destination path
public-directory no public Web document root below the application root
post-deploy-hook no empty Relative PHP hook inside the release
health-url no empty HTTPS URL checked after deployment
protected-paths no .env, storage, .git, .github Newline-separated persistent or private paths
release-id no GitHub SHA/run/attempt Unique protocol identifier
deployment-commit no github.sha Source revision represented by the release
max-archive-bytes no 256 MiB Compressed archive limit
max-uncompressed-bytes no 1 GiB Extracted release limit
max-entries no 100,000 ZIP entry limit

Security properties

  • Explicit FTPS with certificate and hostname verification
  • HTTPS-only deployment and health URLs
  • HMAC-SHA256 over a timestamped, bounded JSON payload
  • Five-minute signature lifetime and a non-blocking server deployment lock
  • SHA-256 verification for the archive and optional environment file
  • Release-specific incoming filenames to prevent cross-run overwrites
  • ZIP path, entry count, size, compression expansion, and Unix symlink validation
  • Source-side rejection of symbolic links and special filesystem entries
  • Protected persistent paths and a reserved action namespace
  • Automatic exclusion of Git metadata and an in-tree environment input
  • Staging validation before application mutation
  • Filesystem rollback when installation or the post-deploy hook fails
  • Fixed public error codes; exception messages and response bodies are never copied into Actions logs
  • Minimal workflow permissions and no dependency on third-party deployment actions

See docs/architecture.md for protocol boundaries and residual risks.

License

MIT

About

Signed ZIP deployments to shared PHP hosting over FTPS

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages