diff --git a/ahoy.yml b/ahoy.yml
index 5045ca05a037c1158457d2717e9ca0a6f200b9bf..f0e6b25f5bbae4153db445d16591f483fa7ab6e0 100644
--- a/ahoy.yml
+++ b/ahoy.yml
@@ -21,9 +21,14 @@ commands:
   start:
     cmd: docker-compose start "$@"
     usage: Run docker-compose start
+  fs:
+    cmd: |
+      docker-compose exec --user root php chown -R www-data:www-data .
+      docker-compose exec --user root php chmod -R g+w .
+    usage: Open shell in the PHP container
   shell:
-    cmd: docker-compose exec --user root php sh "$@"
+    cmd: docker-compose exec --user root php /bin/bash "$@"
     usage: Open shell in the PHP container
   update:
-    cmd: composer d4d "$@"
+    cmd: composer lakedrops:docker4drupal
     usage: Update Docker4Drupal setup in project
diff --git a/composer.json b/composer.json
index 051f18452d4df59c85d0dd5ec3bffc9a0947e5ad..a5fd053cfd4955fd3c31c4adea2dab81c9068b8d 100644
--- a/composer.json
+++ b/composer.json
@@ -25,9 +25,9 @@
     },
     "require": {
         "ext-json": "*",
-        "php": ">=5.6",
+        "php": ">=7.0",
         "composer-plugin-api": "^1.0.0",
-        "lakedrops/composer-json-utils": "^1.2.0",
+        "lakedrops/composer-json-utils": "^1.3.1",
         "lakedrops/composer-scripts": "^1.1.0",
         "lakedrops/docker-traefik": "^1.1.0",
         "lakedrops/dotenv": "^1.0.0",
@@ -47,12 +47,6 @@
     "extra": {
         "class": "LakeDrops\\Docker4Drupal\\Plugin",
         "lakedrops": {
-            "scripts": {
-                "d4d": {
-                    "callback": "LakeDrops\\Docker4Drupal\\Plugin::config",
-                    "description": "(Re-)Configure docker for this project."
-                }
-            },
             "ahoy": {
                 "d4d": {
                     "usage": "Docker for Drupal",
diff --git a/src/CommandProvider.php b/src/CommandProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..fb14a2e30edc81d793b4ed2393a76f6cc2d39c6a
--- /dev/null
+++ b/src/CommandProvider.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace LakeDrops\Docker4Drupal;
+
+use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
+use Composer\Command\BaseCommand;
+
+class CommandProvider implements CommandProviderCapability {
+
+  /**
+   * Retrieves an array of commands
+   *
+   * @return BaseCommand[]
+   */
+  public function getCommands(): array {
+    return [
+      new UpdateCommand(),
+    ];
+  }
+}
diff --git a/src/Handler.php b/src/Handler.php
index 58c1d64814badbff9a09c49c5633593285cb6758..ba97061ef6dc97d972fb15158b054cf4b558c83b 100644
--- a/src/Handler.php
+++ b/src/Handler.php
@@ -2,7 +2,6 @@
 
 namespace LakeDrops\Docker4Drupal;
 
-use Composer\Script\Event;
 use LakeDrops\Component\Composer\BaseHandler;
 use LakeDrops\Component\Dotenv\Dotenv;
 use LakeDrops\DockerTraefik\Traefik;
@@ -24,8 +23,6 @@ class Handler extends BaseHandler {
   /**
    * Configure Drupal Project for Docker.
    *
-   * @param \Composer\Script\Event $event
-   *   The event that triggered the call of this function.
    * @param bool $overwrite
    *   Whether to overwrite existing config files.
    *
@@ -33,10 +30,10 @@ class Handler extends BaseHandler {
    * @throws \Twig_Error_Runtime
    * @throws \Twig_Error_Syntax
    */
-  public function configureProject(Event $event, $overwrite = FALSE) {
+  public function configureProject($overwrite = FALSE) {
 
     // We only do the fancy stuff for developers.
-    if (!$event->isDevMode()) {
+    if (!$this->isDevMode()) {
       return;
     }
 
@@ -98,11 +95,12 @@ class Handler extends BaseHandler {
         $rendered = $twig->render($filename, $options);
         if (!empty($def['add2yaml']) && isset($options[$filename])) {
           $yaml = Yaml::parse($rendered);
+          /** @noinspection SlowArrayOperationsInLoopInspection */
           $yaml = array_merge_recursive($yaml, $options[$filename]);
           $rendered = Yaml::dump($yaml, 9, 2);
         }
         if ($fs->exists($file)) {
-          if (md5_file($file) == md5($rendered)) {
+          if (md5_file($file) === md5($rendered)) {
             continue;
           }
           $orig_file = $file . '.orig';
@@ -120,7 +118,7 @@ class Handler extends BaseHandler {
         }
         file_put_contents($file, $rendered);
       }
-      if (isset($def['link']) && ($def['link'] != $settingsPath)) {
+      if (isset($def['link']) && ($def['link'] !== $settingsPath)) {
         $link = $def['link'] . '/' . $filename;
         if (!$fs->exists($link)) {
           $rel = substr($fs->makePathRelative($file, $projectRoot . '/' . $link), 3, -1);
@@ -132,7 +130,7 @@ class Handler extends BaseHandler {
 
     // Make sure that settings.docker.php gets called from settings.php.
     $settingsPhpFile = $settingsPath . '/settings.php';
-    if ($fs->exists(($settingsPhpFile))) {
+    if ($fs->exists($settingsPhpFile)) {
       $settingsPhp = file_get_contents($settingsPhpFile);
       if (strpos($settingsPhp, 'settings.docker.php') === FALSE) {
         $settingsPhp .= "\n\nif (file_exists(__DIR__ . '/settings.docker.php')) {\n  include __DIR__ . '/settings.docker.php';\n}\n";
@@ -161,7 +159,7 @@ class Handler extends BaseHandler {
    * @return array
    *   List of files.
    */
-  protected function getFiles($projectRoot, $webRoot, $settingsPath) {
+  protected function getFiles($projectRoot, $webRoot, $settingsPath): array {
     return [
       'settings.docker.php' => [
         'dest' => $projectRoot . '/' . $settingsPath,
@@ -200,7 +198,7 @@ class Handler extends BaseHandler {
    * @return array
    *   The options.
    */
-  public function getOptions($key = NULL) {
+  public function getOptions($key = NULL): array {
     if ($this->options === NULL) {
       $env = new Dotenv('docker4drupal', $this->io);
       $projectname = getenv('COMPOSE_PROJECT_NAME');
@@ -321,7 +319,7 @@ class Handler extends BaseHandler {
       $matches = array();
       if (preg_match('/^([a-z0-9]+)(:\d{1,2})?(\s)+Link/', $str, $matches)) {
         $ifname = $matches[1];
-        if (strlen($matches[2]) > 0) {
+        if ($matches[2] !== '') {
           $ifname .= $matches[2];
         }
       }
@@ -333,7 +331,7 @@ class Handler extends BaseHandler {
     if (!isset($interface)) {
       return $local_addrs;
     }
-    return isset($local_addrs[$interface]) ? $local_addrs[$interface] : '127.0.0.1';
+    return $local_addrs[$interface] ?? '127.0.0.1';
   }
 
   /**
@@ -341,7 +339,7 @@ class Handler extends BaseHandler {
    *
    * @return string
    */
-  private function getDockerMountSource($projectRoot) {
+  private function getDockerMountSource($projectRoot): string {
     try {
       $output = [];
       exec('basename "$(cat /proc/1/cpuset)"', $output);
diff --git a/src/NestedArray.php b/src/NestedArray.php
index 2323ec2773f503de613b4131c165570d51c23759..b1be92357c732721d80a428be7f3161ffb418afb 100644
--- a/src/NestedArray.php
+++ b/src/NestedArray.php
@@ -15,7 +15,7 @@ class NestedArray {
    * @return array
    *   The merged array.
    */
-  public static function mergeDeep() {
+  public static function mergeDeep(): array {
     return self::mergeDeepArray(func_get_args());
   }
 
@@ -30,13 +30,14 @@ class NestedArray {
    * @return array
    *   The merged array.
    */
-  public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FALSE) {
+  public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FALSE): array {
     $result = [];
     foreach ($arrays as $array) {
       foreach ($array as $key => $value) {
         if (is_int($key) && !$preserve_integer_keys) {
           $result[] = $value;
         }
+        /** @noinspection NotOptimalIfConditionsInspection */
         elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
           $result[$key] = self::mergeDeepArray([$result[$key], $value], $preserve_integer_keys);
         }
diff --git a/src/Plugin.php b/src/Plugin.php
index 2d6363eaad59976af33c40ad9521420d74b6daf3..5a8fdbc48d7488ff2fe4995f36e58651158e767d 100644
--- a/src/Plugin.php
+++ b/src/Plugin.php
@@ -21,26 +21,25 @@ class Plugin extends BasePlugin {
   /**
    * {@inheritdoc}
    */
-  public static function getSubscribedEvents() {
-    return array(
-      ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject',
-      ScriptEvents::POST_INSTALL_CMD => 'configureProject',
-      ScriptEvents::POST_UPDATE_CMD => 'configureProject',
-    );
+  public function getCapabilities(): array {
+    return [
+      \Composer\Plugin\Capability\CommandProvider::class => CommandProvider::class,
+    ];
   }
 
   /**
-   * Configure project event callback.
-   *
-   * @param \Composer\Script\Event $event
-   *   The event that triggered the call of this function.
+   * {@inheritdoc}
    */
-  public function configureProject(Event $event) {
-    $this->handler->configureProject($event);
+  public static function getSubscribedEvents(): array {
+    return [
+      ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject',
+      ScriptEvents::POST_INSTALL_CMD => 'configureProject',
+      ScriptEvents::POST_UPDATE_CMD => 'configureProject',
+    ];
   }
 
   /**
-   * Script callback for putting in composer scripts to configure the project.
+   * Configure project event callback.
    *
    * @param \Composer\Script\Event $event
    *   The event that triggered the call of this function.
@@ -49,9 +48,12 @@ class Plugin extends BasePlugin {
    * @throws \Twig_Error_Runtime
    * @throws \Twig_Error_Syntax
    */
-  public static function config(Event $event) {
-    $handler = new Handler($event->getComposer(), $event->getIO());
-    $handler->configureProject($event, TRUE);
+  public function configureProject(Event $event) {
+    /** @var Handler $handler */
+    $handler = $this->handler;
+    $handler
+      ->setEvent($event)
+      ->configureProject();
   }
 
 }
diff --git a/src/UpdateCommand.php b/src/UpdateCommand.php
new file mode 100644
index 0000000000000000000000000000000000000000..951bc543b77952fa883606c7242ba4c1da15d05c
--- /dev/null
+++ b/src/UpdateCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace LakeDrops\Docker4Drupal;
+
+use LakeDrops\Component\Composer\BaseCommand;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class UpdateCommand extends BaseCommand {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this->setName('lakedrops:docker4drupal');
+    $this->setDescription('(Re-)Configure Docker for Drupal for this project.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHandlerClass() {
+    return Handler::class;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \Twig_Error_Loader
+   * @throws \Twig_Error_Runtime
+   * @throws \Twig_Error_Syntax
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    parent::execute($input, $output);
+    /** @var Handler $handler */
+    $handler = $this->handler;
+    $handler->configureProject(TRUE);
+  }
+
+}