Push a tag, done: automated TYPO3 deployment with GitHub Actions
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_dirautomatically from yourcomposer.json– no need to set the webroot manually. Make surebin/phppoints 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:
- Generate an SSH key pair locally:
ssh-keygen -t ed25519 -C "github-actions-deploy" - Add the public key to
~/.ssh/authorized_keyson the server - 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:
- GitHub Actions checks out the repository
- Composer installs all dependencies (without dev packages)
- Deployer connects to the server via SSH
- A new release directory is created
- Shared dirs (fileadmin, var/log etc.) are mounted via symlinks
- The
currentsymlink is swapped atomically – zero downtime - TYPO3 cache is flushed
- 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
- Install Deployer 8.x via Composer – the TYPO3 recipe reads
public_dirautomatically fromcomposer.json - Configure
deploy.phpwith host, shared dirs and PHP 8.2 path - Store SSH key as a GitHub Secret
- Create GitHub Actions workflow with tag trigger
- Push a tag → everything runs automatically, zero downtime, rollback available at any time
Share
Leave a comment