Surf

Package name

typo3/surf

Version

main

Language

en

Author

Neos Team & TYPO3 Team & Contributors

License

This document is published under the Creative Commons BY 4.0 license.

Rendered

Sat, 10 May 2025 23:46:27 +0000


This package is a powerful and flexible automated deployment tool for PHP projects. It is best suited for, but by no means limited to, deploying TYPO3, Flow and Neos applications. It is inspired by some features of Capistrano (thank you) in terms of the Git workflow.


Table of Contents:

Introduction

Surf package is a complete automated deployment tool. It is best used but by far not limited to deploy TYPO3 CMS and Flow applications. It's inspired by some features of Capistrano (thanks) concerning the Git workflow.

Some features of Surf:

  • Remote checkout of Git repositories with submodules
  • Flexible, declarative configuration of deployments
  • Multi-node, multi-application, multi-deployment deployments
  • Hook in any deployment stage
  • Create custom tasks with a few lines
  • Simulate deployments with a dry run
  • Easily extendable for other types of applications

Installation

There are several ways to install Surf:

  1. Download phar archive
  2. Global composer installation
  3. Local composer installation
  4. Building a Surf phar from source

Download phar archive

To install Surf as phar archive, run the following commands:

mkdir /usr/local/surf
curl -L https://github.com/TYPO3/Surf/releases/download/3.6.2/surf.phar -o /usr/local/surf/surf.phar
chmod +x /usr/local/surf/surf.phar
ln -s /usr/local/surf/surf.phar /usr/local/bin/surf
Copied!

You may need extended privileges e.g. sudo.

This way, you can add /usr/local/surf to PHP Include Paths in your IDE.

Upgrading Surf

Later, to upgrade Surf, run the command:

surf self-update
Copied!

Global composer installation

To install Surf globally via composer, run the following command:

composer global require typo3/surf:^3.6
Copied!

This way, you can add ~/.composer/vendor/typo3/surf to PHP Include Paths in your IDE.

Local composer installation

To install Surf within your project via composer, run the following command:

composer require typo3/surf:^3.6
Copied!

The advantage of this method is that you can directly inspect the source files of surf without any further configuration in your IDE.

Building a Surf phar from source

Surf is built using humbug/box and the process is simple:

  • Install humbug/box as described in its documentation
  • Clone or download the desired branch of typo3/surf
  • cd your/surf/folder
  • composer install --no-dev
  • path/to/box compile

The generated surf.phar in the folder release should work as expected.

Usage

After installing Surf you have to create one or more deployment configuration files for your project. The deployment configuration files are at the moment just plain php files. So you can do what ever you can dream of what is possible with php itself. We recommend to keep the deployment configuration as simple as possible and do it in the first place in a procedural like style.

Per default Surf expects the deployment configuration files within the .surf directory in your project.

If you like you can specify the configuration directory with the command option --configurationPath (Rollback deployment) or you could add an environment variable called SURF_WORKSPACE.

But for now we are going to place our deployment configuration files in the .surf directory.

We start by creating a simple deployment configuration in ~/.surf/MyDeployment.php for a deployment with name MyDeployment:

<?php
$node = new \TYPO3\Surf\Domain\Model\Node('example');
$node->setHostname('example.com');
$node->setDeploymentPath('/home/my-flow-app/app');
$node->setOption('username', 'myuser');

$application = new \TYPO3\Surf\Application\Neos\Flow();
$application->setVersion('4.0');
$application->setOption('repositoryUrl', 'git@github.com:myuser/my-flow-app.git');
$application->addNode($node);

$deployment->addApplication($application);
Copied!

That's a very basic deployment based on the default Flow application template TYPO3\Surf\Application\Neos\Flow. The deployment object is available to the script as the variable $deployment. A node is basically a deployment target representing a server for an application. The node is assigned to the applications for the deployment. Finally the application is added to the deployment. Depending on the usecase, the deployment path can also be set in the application object which is then set for all nodes. This is especially useful if you have several applications which you want to deploy to the same node for example a backend and frontend application.

Each application resembles a repository with code. So a more complex deployment could both deploy a Flow application and release an extension for a TYPO3 CMS website. Also different roles can be expressed using applications, since every task can be registered to run for all or a specific application instance.

In this basic deployment above are a lot of sensitive defaults configured behind the curtains. You are going to explore them in the different chapters of the documentation.

SSH Authentication Types

The preferred way of connecting to the remote host is via SSH Public-Key authentication. That's why in the example above, only the username and hostname are set.

However, due to constraints in the infrastructure setup, sometimes, deployment scenarios do not work with public key authentication. Surf also supports password-based SSH authentication. For that, you need to specify the password as follows:

$node->setOption('password', 'yourSshPasswordHere');
Copied!

Authentication with passwords needs the expect unix tool which is installed by default in most Linux distributions.

Custom Connection

In case you need to connect to the remote host via more esoteric protocols, you can also implement your own remote host connection: In this case, set the option remoteCommandExecutionHandler on the node:

$node->setOption('remoteCommandExecutionHandler', function(\TYPO3\Surf\Domain\Service\ShellCommandService $shellCommandService, $command, Node $node, Deployment $deployment, $logOutput = TRUE) {
   // Now, do what you need to do in order to connect to $node and execute $command.
   // You can call $shellCommandService->executeProcess() here.

   // This function should return a two-element array where the first array element
   // is an integer containing the Exit Code, and the second array element is a
   // string with the full, trimmed, output.
});
Copied!

Test a deployment

You can get a description of the deployment by running:

$ surf describe MyDeployment
Copied!

Simulate the deployment by running:

$ surf simulate MyDeployment
Copied!

The simulation gives a hint which tasks will be executed on which node. During simulation no harmful tasks will be executed for real. If a remote SSH command would be executed it will be printed in the log messages starting with ... $nodeName: "command".

But be aware, if the simulation is working fine it does not mean everything is working correctly for the real deployment. Sorry for that. We are working on it to test really everything during the simulation. But this task is difficult, we tell you.

Run a deployment

If everything looks right, you can run the deployment:

$ surf deploy MyDeployment
Copied!

To include extra details in the output, you can increase verbosity with the --verbose option:

  • -v for normal output,
  • -vv for more verbose output,
  • -vvv for debug.

Surf is going to create the following directories on the deployment host:

  • releases contains releases dirs,
  • shared contains shared files and dirs,
  • releases/next (temporarily)
  • releases/current symlink to current release
  • releases/previous (optional)

Configure your hosts to serve your public directory from current.

By default Surf keeps the all the releases, but you can configure this number by modifying the associated option:

$application->setOption('keepReleases', 2);
Copied!

Customization

Using git for deployment

By default Surf uses rsync and composer for deployment. But you can also use git, by adding the following configuration to your Application:

$application->setOption('transferMethod', 'git');
$application->setOption('packageMethod', NULL);
$application->setOption('updateMethod', NULL);
Copied!

Using rsync can speed up your deployment and doesn't require composer and git on the production server.

Architecture

In order to better understand the concept of the Surf deployment process we will explain some fundamental terminology in this section.

Basically you should grasp the following four main terms to understand the basic concept of the deployment process:

Workflow

The workflow defines the execution process of the deployment and consists of a number of stages and tasks for every stage. Surf ships already with one concrete Workflow called SimpleWorkflow. But you can define your own workflow as long as your workflow class inherits from the \TYPO3\Surf\Domain\Model\Workflow class shipped with Surf.

You assign the workflow to the deployment class.

Application

The application is the code you want to ship. The deployment configuration has at least one application. But you can configure as many applications as you want in one deployment configuration. The application itself contains of one or more nodes. You can also define within the workflow whether a task is to apply to all applications or only to a specific application.

Node

A node is basically a deployment target representing a server for an application. The node is assigned to an application for the deployment.

Task

A task is the smallest unit in the whole deployment process. A task consists of commands either executed locally or remotely.

A task can be applied for all applications or only for certain applications.

Deployment Flow

If your deployment configuration is not overriding the workflow option, then you are using the default SimpleWorkflow class shipped with Surf.

The SimpleWorkflow class defines 9 stages of the deployment process which are sequentially called. Each stage can consists of none, one or multiple tasks running one after another.

You can add your own tasks for each stage. If you like, you can also specify if your custom task is running before or after a task already defined for this stage.

In the list below you can see all the 9 steps defined by the SimpleWorkflow:

initialize

This is normally used only for an initial deployment to an instance. At this stage you may prefill certain directories for example.

Example Task: \TYPO3\Surf\Task\CreateDirectoriesTask

package

This stage is where you normally package all files and assets, which will be transferred to the next stage.

Example Task: \TYPO3\Surf\Task\Package\GitTask

transfer

Here all tasks are located which serve to transfer the assets from your local computer to the node, where the application runs.

Example Task: \TYPO3\Surf\Task\Transfer\RsyncTask

update

If necessary, the transferred assets can be updated at this stage on the foreign instance.

Example Task: \TYPO3\Surf\Task\TYPO3\CMS\SymlinkDataTask

migrate

Here you can define tasks to do some database updates / migrations. Be careful and do not delete old tables or columns, because the old code, relying on these, is still live.

Example Task: \TYPO3\Surf\Task\TYPO3\CMS\SetUpExtensionsTask

finalize

This stage is meant for tasks, that should be done short before going live, like cache warm ups and so on.

Example Task: \TYPO3\Surf\Task\Neos\Flow\PublishResourcesTask

test

In the test stage you can make tests, to check if everything is fine before switching the releases.

Example Task: \TYPO3\Surf\Task\Test\HttpTestTask

switch

This is the crucial stage. Here the old live instance is switched with the new prepared instance. Normally the new instance is symlinked.

Example Task: \TYPO3\Surf\Task\SymlinkReleaseTask

cleanup

At this stage you would cleanup old releases or remove other unused stuff.

Example Task: \TYPO3\Surf\Task\CleanupReleasesTask

You can create your own workflow if you like. In order to do so you have to extend the abstract Workflow class. The creation of a custom workflow is out of the scope of this chapter. Have a look at the SimpleWorkflow in oder to do so.

Manipulate the flow

If you like to add your own tasks to a specific stage of the flow, you can just add them the following ways:

// Add tasks to a specific stage
$workflow->addTask('YourTask', 'cleanup');

// Add tasks that shall be executed after the given stage
$workflow->afterStage('YourTask', 'cleanup');

// Add tasks that shall be executed before the given stage
$workflow->beforeStage('YourTask', 'cleanup');

// Add tasks that shall be executed before the given task
$workflow->beforeTask(AnotherTask::class, 'YourTask');

// Add tasks that shall be executed after the given task
$workflow->afterTask(AnotherTask::class, 'YourTask');
Copied!

If you like to remove certain tasks from the flow, just do it like that:

// You remove the given task from every application
$workflow->removeTask(FlushCachesTask::class);

// Only remove the task for a specific application
$workflow->removeTask(FlushCachesTask::class, $application);
Copied!

Applications

The application is the code you want to ship. The deployment configuration has at least one application. But you can configure as many applications as you want in one deployment configuration. The application itself contains of one or more nodes. You can also define within the workflow whether a task is to apply to all applications or only to a specific application.

Surf already ships with specific applications with a sensitive default configuration for the execution process (which tasks are to be called in which stage).

For example there is one application class for TYPO3 or Neos shipped with Surf. But you can create your own specific Application class as long as it inherits from \TYPO3\Surf\Domain\Model\Application.

By default (as long as the application inherits from \\TYPO3\\Surf\\Application\\BaseApplication) an application uses rsync and composer as transfer and package method. But you can also use git, by adding the following configuration to your application:

$application->setOption('transferMethod', 'git');
$application->setOption('packageMethod', NULL);
$application->setOption('updateMethod', NULL);
Copied!

Application options

This is a (so far) incomplete list of options for \TYPO3\Surf\Application\BaseApplication.

Help documenting them all! Click the button "Edit on Github".

Special Use Cases

Applying Cherry-Picks to Git Repositories: Post-Checkout commands

When you want to execute some commands directly after checkout, such as cherry-picking not-yet-committed bugfixes, you can set the gitPostCheckoutCommands option on the application, being a two-dimensional array.

The key contains the path where the command shall execute, and the value is another array containing the commands themselves.

Example:

$application->setOption('gitPostCheckoutCommands', [
   'Packages/Framework/Neos.Flow/' => [
      'git fetch https://github.com/neos/flow-development-collection.git refs/heads/somefix',
      'git cherry-pick FETCH_HEAD'
   ]
]);
Copied!

Nodes

A node is basically a deployment target representing a server for an application. The node is assigned to an application for the deployment.

A simple node configuration looks like this:

<?php

$node = new \TYPO3\Surf\Domain\Model\Node('example');
$node->setHostname('example.com');
$node->setOption('username', 'myuser');

$application->addNode($node);

Copied!

SSH Authentication Types

The preferred way of connecting to the remote host is via SSH Public-Key authentication. That's why in the example above, only the username and hostname are set.

However, due to constraints in the infrastructure setup, sometimes, deployment scenarios do not work with public key authentication. Surf also supports password-based SSH authentication. For that, you need to specify the password as follows:

<?php

$node->setOption('password', 'yourSshPasswordHere');
Copied!

Custom Connection

In case you need to connect to the remote host via more esoteric protocols, you can also implement your own remote host connection: In this case, set the option remoteCommandExecutionHandler on the node:

<?php

$node->setOption('remoteCommandExecutionHandler', function(ShellCommandService $shellCommandService, $command, Node $node, Deployment $deployment, $logOutput = TRUE) {
   // Now, do what you need to do in order to connect to $node and execute $command.
   // You can call $shellCommandService->executeProcess() here.

   // This function should return a two-element array where the first array element
   // is an integer containing the Exit Code, and the second array element is a
   // string with the full, trimmed, output.
});

Copied!

Some providers may have SSH rate limits

Source and details: https://karsten.dambekalns.de/blog/using-ssh-controlmaster-with-typo3-surf.html

Add something like this to  /.ssh/config to reuse existing SSH connections:

Host myhost.uberspace.de
ControlMaster auto
ControlPath /tmp/ssh_mux_%h_%p_%r
ControlPersist 600
Copied!

Tasks

Since a deployment configuration is just a plain PHP file you can create custom tasks by creating for example a NpmInstallTask class which itself must extend in the whole inheritance chain the abstract class \\TYPO3\\Surf\\Domain\\Model\\Task shipped with Surf:

<?php

namespace Vendor\MyNamespace;

use TYPO3\Surf\Domain\Model\Application;
use TYPO3\Surf\Domain\Model\Deployment;
use TYPO3\Surf\Domain\Model\Node;
use TYPO3\Surf\Task\LocalShellTask

class NpmInstallTask extends LocalShellTask
{
    /**
     * Executes this action
     *
     * @param \TYPO3\Surf\Domain\Model\Node $node
     * @param \TYPO3\Surf\Domain\Model\Application $application
     * @param \TYPO3\Surf\Domain\Model\Deployment $deployment
     * @param array $options
     */
    public function execute(Node $node, Application $application, Deployment $deployment, array $options = [])
    {
        if (!isset($options['command'])) {
            $options['command'] = 'cd {workspacePath} && npm install';
        }

        parent::execute($node, $application, $deployment, $options);
    }
}

Copied!

In this case we create a task to install the npm dependencies locally. The example shows some simple things to be aware of. First of all you see the string {workspacePath}. This placeholder gets replaced by the full workspace path for the current application.

We will see later that for the remote shell tasks, we have more of these placeholders (Placeholders). Secondly we see that the task defines a default command if none is specified. If you like you can override this option in your deployment configuration. This is valid for all the delivered tasks shipped with Surf.

For this simple task above, we recommend to simplify this by just using the possibility to define your task dynamically by the following mechanism provided by Surf:

<?php

...

$workflow->defineTask('NpmInstallTask', \TYPO3\Surf\Task\LocalShellTask::class, [
    'command' => [
        'cd {workspacePath} && npm install',
    ]
]);
Copied!

This way you create a task dynamically by extending the base task \TYPO3\Surf\Task\LocalShellTask::class with an array of options. In this case the command option is mandatory for the LocalShellTask.

We will show you another convenient and often used way to customize the deployment workflow with your own tasks:

<?php

...

$workflow->defineTask('CopyEnvFileTask', \TYPO3\Surf\Task\ShellTask::class, [
    'command' => [
        'cp {sharedPath}/.env {releasePath}/.env',
        'cd {releasePath}',
    ]
]);
Copied!

In this case we create a task dynamically based on the ShellTask. The ShellTask execute one or more provided commands on the target machine. As you can see, we have used some other placeholders compared to the LocalShellTask above. For the remote ShellTask the following placeholders are available:

Placeholders

  • workspacePath: The path to the local workspace directory
  • deploymentPath: The path to the deployment base directory
  • releasePath: The path to the release directory in work (typically referenced by next)
  • sharedPath: The path to the shared directory for all releases
  • currentPath: The path that points to the current release
  • previousPath: The path that points to the previous release

Add task to the deployment flow

So we have seen how to create custom tasks in different ways. In the following we will see how we add these tasks to the deployment flow:

<?php
/** @var \TYPO3\Surf\Domain\Model\Deployment $deployment */

$application = new \TYPO3\Surf\Application\TYPO3\CMS();
$deployment->addApplication($application);
$workflow = $deployment->getWorkflow();

$workflow->defineTask('CopyEnvFileTask', \TYPO3\Surf\Task\ShellTask::class, [
    'command' => [
        'cp {sharedPath}/.env {releasePath}/.env',
        'cd {releasePath}',
    ]
]);

$deployment->onInitialize(function() use ($workflow, $application) {
    $workflow->afterStage('transfer', 'CopyEnvFileTask', $application);
});
Copied!

This will execute the new task after the stage transfer only for the application referenced by $application.

Besides specifying the execution point via a stage, you can also give an existing task as an anchor and specify the task execution with afterTask or beforeTask:

<?php

$workflow->beforeTask(SomeTask::class,
    [
        'CopyEnvFileTask'
    ]
);

Copied!

The following table shows all the methods to manipulate the tasks in the deployment flow (part of the abstract Workflow class):

Method Arguments Description
defineTask $taskName, $taskType, ($options) Defines a new task with name $taskName based on $taskType with custom options.
addTask $tasks, $stage, ($application) Add one or more tasks to the workflow that should run in the given stage.
removeTask $taskName Removes the task with the given name from all stages and applications.
afterTask $taskName, $tasks, ($application) Adds one or more tasks that should run after the given task name.
beforeTask $taskName, $tasks, ($application) Adds one or more tasks that should run before the given task name.

Options for Task

In order to customize options of existing tasks you can do it the following ways:

<?php

use TYPO3\Surf\Task\Transfer\RsyncTask;

// Customize the option for the task only for a specific application
$application->setOption(RsyncTask::class . '[rsyncExcludes]', [
    '.git',
    'public/fileadmin',
]);

// Customize the option for the task only for a specific node
$node->setOption(RsyncTask::class . '[rsyncExcludes]', [
    'public/fileadmin',
]);

// Customize the option for the whole deployment
$deployment->setOption(RsyncTask::class . '[rsyncExcludes]', [
    '.git',
]);

Copied!

The order is important because application options override node options and node options override deployment options.

CLI Usage

After installation of Surf you will have the ability to run the surf command from your terminal.

Surf will by default check for your deployment configurations in the subfolder .surf.

To get list of all available tasks run the surf command:

TYPO3 Surf [version]

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  deploy    Deploys the given name
  describe  Describes the flow for the given name
  help      Displays help for a command
  list      Lists commands
  migrate   Migrates old deployment definitions to new Surf version
  rollback  Rollback current to previous release and remove current folder
  show      Shows all the deployments depending on the directory configuration
  simulate  Simulates the deployment for the given name

Copied!

List available deployments

You can get a list of available deployments by running:

$ surf show
Copied!

Test a deployment

You can get a description of the deployment by running:

$ surf describe MyDeployment
Copied!

Simulate the deployment by running:

$ surf simulate MyDeployment
Copied!

The simulation gives a hint which tasks will be executed on which node. During simulation no harmful tasks will be executed for real. If a remote SSH command would be executed it will be printed in the log messages starting with ... $nodeName: "command".

Run a deployment

If everything looks right, you can run the deployment:

$ surf deploy MyDeployment
Copied!

Rollback deployment

If your release does not meet your demand, you can quickly rollback to the previous one:

$ surf rollback MyDeployment
Copied!

Using a different configuration path

If you want to use a different configuration path than .surf use the provided option like this:

$ surf show --configurationPath myConfigurationPath
Copied!

Smoke Testing

As you do automated deployments, you should check if the website is up and running before switching it to the live site. This is called a Smoke Test. We will give an example for using the built-in HTTP smoke test.

First, you need to create a virtual host with document root in "<deploymentDirectory>/releases/next/Web". While a deployment is running, the new website will be available under this URL and can be used for testing.

Then, add a test as follows to the deployment configuration:

<?php

$workflow = new \TYPO3\Surf\Domain\Model\SimpleWorkflow();

$smokeTestOptions = [
	'url' => 'http://your-website.com',
	'remote' => true,
	'expectedStatus' => 200,
	'expectedRegexp' => '/somethingYouExpectOnThePage/'
];
$workflow->defineTask('MyCompany\\MyPackage\\SmokeTest', \TYPO3\Surf\Task\Test\HttpTestTask::class, $smokeTestOptions);

$workflow->addTask('MyCompany\\MyPackage\\SmokeTest', 'test', $application);
Copied!

The HTTP test has the following options:

Most important options:

  • url (required): URL which should be loaded
  • remote: if TRUE, the smoke test is triggered through the SSH channel on the remote host via command-line CURL. If false, it is triggered from the deploying host.
  • expectedStatus: expected HTTP status code
  • expectedHeaders: HTTP Header Strings which are expected (can be a multiline string, each header being on a separate line)
  • expectedRegexp: Regular Expression to test the contents of the HTTP response against

Further options:

  • timeout (only if remote=FALSE): HTTP timeout to use
  • port (only if remote=FALSE): HTTP Port to use
  • method (only if remote=FALSE): HTTP method to use (default GET)
  • username (only if remote=FALSE): HTTP Authentication username
  • password (only if remote=FALSE): HTTP Authentication Password
  • data (only if remote=FALSE): HTTP payload
  • proxy (only if remote=FALSE): HTTP Proxy to use
  • proxyPort (only if remote=FALSE): HTTP Proxy port to use
  • additionalCurlParameters (only if remote=TRUE): list of parameters which is directly passed to CURL. Especially useful to e.g. disable SSL certificate check (with --insecure)

Tests in test stage and caches

In the test stage, the caches of the application is not flushed in order not to affect the live page. A possible solution is to disable caches when running smoke tests in the test stage on "next" release.

TYPO3

AdditionalConfiguration.php:

if ($context->isTesting()){
	foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] as $cacheName => $cacheConfiguration) {
		$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$cacheName]['backend'] = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
	}
}
Copied!

.htaccess:

# Add Rewrite rule near https://github.com/TYPO3/typo3/blob/main/typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/root-htaccess#L270
RewriteCond %{HTTP_HOST} ^next\.example\.com$
RewriteRule .? - [E=TYPO3_CONTEXT:Testing]

Copied!

If you are not able to set environment variables via .htaccess, you can use composer autoloading and a PHP file. Thanks to the team of jweiland.net for this solution.

Root composer.json:

{
	"autoload": {
		"files": ["scripts/typo3context.php"]
	}
}
Copied!

typo3context.php:

<?php
// Set the application context in this file because it is not possible to set environment variables
// via .htaccess e.g. on domainFACTORY/jweiland.net servers
$context = 'Production';

// detect application context by domain
if (array_key_exists('HTTP_HOST', $_SERVER)) {
	if (0 === strpos($_SERVER['HTTP_HOST'], 'next.')) {
		$context = 'Testing';
	}
}
putenv('TYPO3_CONTEXT=' . $context);
Copied!

How to deploy TYPO3 websites

If you would like to deploy a TYPO3 website a good starting point is to use TYPO3CMS Application class provided by Surf:

<?php
/** @var \TYPO3\Surf\Domain\Model\Deployment $deployment */

$node = new \TYPO3\Surf\Domain\Model\Node('my.node.com');
$node
    ->setHostname($node->getName())
    ->setDeploymentPath('/httpdocs')
    ->setOption('username', 'myuser')
    ->setOption('phpBinaryPathAndFilename', '/usr/local/bin/php_cli');

$application = new \TYPO3\Surf\Application\TYPO3\CMS();
$application
    ->setOption('baseUrl', 'https://my.node.com/')
    ->setOption('webDirectory', 'public')
    ->setOption('symlinkDataFolders', ['fileadmin'])
    ->setOption('repositoryUrl', 'file://' . dirname(__DIR__))
    ->setOption('keepReleases', 3)
    ->setOption('composerCommandPath', 'composer')
    ->setOption('rsyncExcludes', [
        '.ddev',
        '.git',
        $application->getOption('webDirectory') . '/fileadmin',
        'packages/**.sass'
    ])
    ->addSymlink($application->getOption('webDirectory') . '/typo3conf/LocalConfiguration.php', '../../../../shared/Configuration/LocalConfiguration.php')
    ->addNode($node);

$deployment
    ->addApplication($application)
    ->onInitialize(
        function () use ($deployment, $application) {
            $deployment->getWorkflow()
                ->beforeTask(\TYPO3\Surf\Task\TYPO3\CMS\SetUpExtensionsTask::class, \TYPO3\Surf\Task\TYPO3\CMS\CompareDatabaseTask::class, $application)
                ->beforeStage('transfer', \TYPO3\Surf\Task\Php\WebOpcacheResetCreateScriptTask::class, $application)
                ->afterStage('switch', \TYPO3\Surf\Task\Php\WebOpcacheResetExecuteTask::class, $application);
        }
    );
Copied!

How to deploy Neos websites

If you would like to deploy a Neos website a good starting point is to use the Neos Application class provided by Surf:

<?php
/** @var \TYPO3\Surf\Domain\Model\Deployment $deployment */

$node = new \TYPO3\Surf\Domain\Model\Node('production');
$node
   ->setHostname('my.node.com')
   ->setDeploymentPath('/var/www/html/my.node.com')
   ->setOption('username', 'myuser');

$application = new \TYPO3\Surf\Application\Neos\Neos('My Node');
$application
   ->setOption('keepReleases', 3)
   ->setOption('composerCommandPath', 'composer')
   ->setOption('repositoryUrl', '<my repository url>')
   ->setOption('branch', 'main')
   ->setOption('updateMethod', null)
   ->setOption('baseUrl', 'https://my.node.com')
   ->setOption('flushCacheList', [
       'Neos_Fusion_Content',
       'Neos_Neos_Fusion'
   ])
   ->addNode($node);

$deployment
   ->addApplication($application)
   ->onInitialize(
      function () use ($deployment, $application) {
            $deployment->getWorkflow()
               ->beforeStage('transfer', \TYPO3\Surf\Task\Php\WebOpcacheResetCreateScriptTask::class)
               ->afterStage('switch', \TYPO3\Surf\Task\Php\WebOpcacheResetExecuteTask::class)
               ->afterStage('switch', \TYPO3\Surf\Task\Neos\Flow\FlushCacheListTask::class);
      }
   );
Copied!

How to deploy Flow Apps

Flow version options

The Flow version used in a project can be set using:

<?php
/** @var \TYPO3\Surf\Domain\Model\Deployment $deployment */

$application = new \TYPO3\Surf\Application\Neos\Flow();
$application->setVersion('4.0');
Copied!

It defaults to 4.0, so if you are using an older Flow version, you need to set the version as x.y. This switches Surf behavior to call Flow commands correctly.

Flow Configuration overrides

If the configuration of a Flow application should be different depending on the deployment configuration (e.g. database settings or external services) the TYPO3\Surf\Task\Neos\Flow\CopyConfigurationTask task can be used to override configuration after the code update (Git checkout).

If a Configuration folder exists inside a folder named after your deployment ~/.surf/deployments/MyDeployment every file in there will be copied to the release Configuration folder recursively.

How to deploy Laravel applications

If you would like to deploy a Laravel application a good starting point is to use Laravel Application class provided by Surf:

<?php
/** @var \TYPO3\Surf\Domain\Model\Deployment $deployment */

$node = new \TYPO3\Surf\Domain\Model\Node('my.node.com');
$node
    ->setHostname($node->getName())
    ->setOption('username', 'myuser')
    ->setOption('phpBinaryPathAndFilename', '/usr/bin/php8.0');

$application = new \TYPO3\Surf\Application\Laravel();
$application
    ->setDeploymentPath('/var/www/html')
    ->setOption('baseUrl', 'https://my.node.com/')
    ->setOption('repositoryUrl', 'file://' . dirname(__DIR__))
    ->setOption('keepReleases', 3)
    ->setOption('composerCommandPath', 'composer')
    ->addNode($node);

$deployment
    ->addApplication($application)
    ->onInitialize(
        function () use ($deployment, $application) {
            $deployment->getWorkflow()
                ->beforeStage('transfer', \TYPO3\Surf\Task\Php\WebOpcacheResetCreateScriptTask::class, $application)
                ->afterStage('switch', \TYPO3\Surf\Task\Php\WebOpcacheResetExecuteTask::class, $application);
        }
    );
Copied!

How to deploy other apps and websites

There are several ways to deploy other projects with TYPO3 Surf. For example, you can:

  • Provide a composer package with the application and tasks you need. Particularly useful for complex workflows.
  • Use \TYPO3\Surf\Application\BaseApplication or \TYPO3\Surf\Domain\Model\Application in your recipe and enrich the workflow with necessary tasks.

Migration from 0.9.x to 2.0

  1. Move deployment scripts form Build/Surf to ~/.surf/deployments
  2. Rename task or use migrate command to switch to new task names
  3. Set transferMethod and packageMethod options in your application, as the default changed from git to rsync
  4. Change options for CreateDirectoriesTask: Now the specified directories are based on the application's release path not the general deployment path (which did not make much sense)
  5. Neos CMS only: Add the task TYPO3\Surf\Task\Neos\Neos\ImportSiteTask to the step migrate again if you use the Neos Application and need it to import your content after each deployment

Sitemap

Index