Push a tag, done: automated TYPO3 deployment with GitHub Actions
Typo3

Push a tag, done: automated TYPO3 deployment with GitHub Actions

Yannick Aister 4 min read

Why automated deployments?

Manual deployment over SFTP is a thing of the past. Uploading files one by one, running Composer on the server, manually flushing the cache – it takes time, is error-prone, and hard to document. With GitHub Actions and Deployer, all of that happens automatically the moment I push a Git tag.

The result: a reproducible, reliable deploy process – with zero downtime, automatic rollback, and no manual steps required.

What we're building

The flow looks like this: I push a tag in Git (e.g. v1.2.0), GitHub Actions starts automatically, builds the project, and Deployer transfers everything to the server via SSH. TYPO3 v14 runs without any downtime throughout.

Prerequisites

  • TYPO3 v14 as a Composer-based project
  • PHP 8.2 or higher on the server (required for TYPO3 v14)
  • SSH access to your shared hosting
  • GitHub repository with Actions enabled

Step 1: Install Deployer

Deployer is installed locally as a Composer dev dependency:

 

composer require --dev deployer/deployer

 

Step 2: Configure deploy.php

The deploy.php in the project root is the heart of the setup. It uses the official TYPO3 recipe from Deployer 8.x, which automatically reads public_dir and bin/typo3 from your composer.json:

 

<?php
namespace Deployer;

require 'recipe/typo3.php';

// Project name
set('application', 'my-typo3-project');

// Git repository
set('repository', 'git@github.com:your-user/your-repo.git');

// Number of releases to keep (for rollback)
set('keep_releases', 3);

// Composer options for production
set('composer_options', '--no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader');

// Shared dirs – persist across releases
add('shared_dirs', [
    'public/fileadmin',
    'public/typo3temp/assets',
    'var/log',
    'var/lock',
    'var/session',
]);

// Shared files
add('shared_files', [
    '.env',
    'public/.htaccess',
]);

// Writable dirs
add('writable_dirs', [
    'public/fileadmin',
    'public/typo3temp',
    'var',
]);

// Host configuration
host('production')
    ->setHostname('yourserver.example.com')
    ->setRemoteUser('ssh-user')
    ->setPort(22)
    ->set('deploy_path', '/var/www/html/my-project')
    ->set('branch', 'main')
    ->set('bin/php', '/usr/bin/php8.2')  // PHP 8.2+ required for TYPO3 v14
    ->set('writable_mode', 'chmod');

// Flush TYPO3 cache after deploy
// After deploy:publish (contains symlink + unlock + cleanup)
after('deploy:publish', 'typo3:cache:flush');

// Unlock on failed deploy
after('deploy:failed', 'deploy:unlock');

TYPO3 v14 note: The recipe reads public_dir automatically from your composer.json – no need to set the webroot manually. Make sure bin/php points to PHP 8.2 or higher.

Shared hosting: symlink caveat

On some shared hosting environments the web server ignores symlinks or caches the old path. If the old version is still being served after a deploy, check whether your hosting provider allows symlinks in the document root. If not, you can configure Deployer to use the release directory directly as the document root – or ask your provider whether FollowSymLinks is enabled in the Apache configuration.

Step 3: Store the SSH key in GitHub Actions

GitHub Actions needs an SSH key to authenticate with the server. Store the private key as a GitHub Secret:

  1. Generate an SSH key pair locally: ssh-keygen -t ed25519 -C "github-actions-deploy"
  2. Add the public key to ~/.ssh/authorized_keys on the server
  3. Add the private key in GitHub under Settings → Secrets → Actions as SSH_PRIVATE_KEY

Step 4: GitHub Actions workflow

The workflow file goes to .github/workflows/deploy.yml:

 

name: Deploy to Production

on:
  push:
    tags:
      - 'v*'  # Trigger only on tags like v1.0.0, v1.2.3 etc.

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup PHP 8.2
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          tools: composer:v2

      - name: Install Composer Dependencies
        run: composer install --no-dev --prefer-dist --no-progress --optimize-autoloader

      - name: Setup SSH Agent
        uses: webfactory/ssh-agent@v0.9.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Add Server to known_hosts
        run: ssh-keyscan -H yourserver.example.com >> ~/.ssh/known_hosts

      - name: Deploy with Deployer
        run: ./vendor/bin/dep deploy production --tag=${{ github.ref_name }} -vv

 

What happens during a deploy

Once I push a tag, the following happens automatically:

  1. GitHub Actions checks out the repository
  2. Composer installs all dependencies (without dev packages)
  3. Deployer connects to the server via SSH
  4. A new release directory is created
  5. Shared dirs (fileadmin, var/log etc.) are mounted via symlinks
  6. The current symlink is swapped atomically – zero downtime
  7. TYPO3 cache is flushed
  8. Old releases are cleaned up (3 are kept)

Rollback in one line

If something goes wrong, rolling back is trivial:

 

./vendor/bin/dep rollback production

 

Deployer switches the symlink back to the previous release – the site is back up immediately.

TL;DR

  1. Install Deployer 8.x via Composer – the TYPO3 recipe reads public_dir automatically from composer.json
  2. Configure deploy.php with host, shared dirs and PHP 8.2 path
  3. Store SSH key as a GitHub Secret
  4. Create GitHub Actions workflow with tag trigger
  5. Push a tag → everything runs automatically, zero downtime, rollback available at any time

Leave a comment