diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index d89c2a24..3914070e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -40,5 +40,8 @@ jobs:
       - name: Create sqlite database
         run: touch storage/database-test.sqlite
 
+      - name: Set up the .env file
+        run: touch .env
+
       - name: Run test suite
         run: php artisan test
diff --git a/app/Facades/SSH.php b/app/Facades/SSH.php
index 5cbb0a60..7d0cde31 100644
--- a/app/Facades/SSH.php
+++ b/app/Facades/SSH.php
@@ -14,6 +14,9 @@
  * @method static setLog(?ServerLog $log)
  * @method static connect()
  * @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false, callable $streamCallback = null)
+ * @method static string upload(string $local, string $remote, ?string $owner = null)
+ * @method static string download(string $local, string $remote)
+ * @method static string write(string $path, string $content, string $owner = null)
  * @method static string assertExecuted(array|string $commands)
  * @method static string assertExecutedContains(string $command)
  * @method static string assertFileUploaded(string $toPath, ?string $content = null)
diff --git a/app/Helpers/SSH.php b/app/Helpers/SSH.php
index 06e79333..db233358 100755
--- a/app/Helpers/SSH.php
+++ b/app/Helpers/SSH.php
@@ -157,7 +157,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
     /**
      * @throws Throwable
      */
-    public function upload(string $local, string $remote): void
+    public function upload(string $local, string $remote, ?string $owner = null): void
     {
         $this->log = null;
 
@@ -165,7 +165,17 @@ public function upload(string $local, string $remote): void
             $this->connect(true);
         }
 
-        $this->connection->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
+        $tmpName = Str::random(10).strtotime('now');
+        $tempPath = home_path($this->user).'/'.$tmpName;
+
+        $this->connection->put($tempPath, $local, SFTP::SOURCE_LOCAL_FILE);
+
+        $this->exec(sprintf('sudo mv %s %s', $tempPath, $remote));
+        if (! $owner) {
+            $owner = $this->user;
+        }
+        $this->exec(sprintf('sudo chown %s:%s %s', $owner, $owner, $remote));
+        $this->exec(sprintf('sudo chmod 644 %s', $remote));
     }
 
     /**
@@ -185,22 +195,15 @@ public function download(string $local, string $remote): void
     /**
      * @throws SSHError
      */
-    public function write(string $remotePath, string $content, bool $sudo = false): void
+    public function write(string $remotePath, string $content, ?string $owner = null): void
     {
         $tmpName = Str::random(10).strtotime('now');
 
         try {
             /** @var FilesystemAdapter $storageDisk */
             $storageDisk = Storage::disk('local');
-
             $storageDisk->put($tmpName, $content);
-
-            if ($sudo) {
-                $this->upload($storageDisk->path($tmpName), sprintf('/home/%s/%s', $this->server->ssh_user, $tmpName));
-                $this->exec(sprintf('sudo mv /home/%s/%s %s', $this->server->ssh_user, $tmpName, $remotePath));
-            } else {
-                $this->upload($storageDisk->path($tmpName), $remotePath);
-            }
+            $this->upload($storageDisk->path($tmpName), $remotePath, $owner);
         } catch (Throwable $e) {
             throw new SSHCommandError(
                 message: $e->getMessage()
diff --git a/app/SSH/OS/OS.php b/app/SSH/OS/OS.php
index f5b57fcc..934017a5 100644
--- a/app/SSH/OS/OS.php
+++ b/app/SSH/OS/OS.php
@@ -3,14 +3,9 @@
 namespace App\SSH\OS;
 
 use App\Exceptions\SSHError;
-use App\Exceptions\SSHUploadFailed;
 use App\Models\Server;
 use App\Models\ServerLog;
 use App\Models\Site;
-use Illuminate\Filesystem\FilesystemAdapter;
-use Illuminate\Support\Facades\Storage;
-use Illuminate\Support\Str;
-use Throwable;
 
 class OS
 {
@@ -178,27 +173,8 @@ public function reboot(): void
     }
 
     /**
-     * @throws SSHUploadFailed
-     */
-    public function editFile(string $path, ?string $content = null): void
-    {
-        $tmpName = Str::random(10).strtotime('now');
-        try {
-            /** @var FilesystemAdapter $storageDisk */
-            $storageDisk = Storage::disk('local');
-            $storageDisk->put($tmpName, $content);
-            $this->server->ssh()->upload(
-                $storageDisk->path($tmpName),
-                $path
-            );
-        } catch (Throwable) {
-            throw new SSHUploadFailed;
-        } finally {
-            $this->deleteTempFile($tmpName);
-        }
-    }
-
-    /**
+     * @deprecated use write() instead
+     *
      * @throws SSHError
      */
     public function editFileAs(string $path, string $user, ?string $content = null): void
@@ -349,9 +325,10 @@ public function ls(string $path, ?string $user = null): string
      */
     public function write(string $path, string $content, ?string $user = null): void
     {
-        $this->server->ssh($user)->write(
+        $this->server->ssh()->write(
             $path,
-            $content
+            $content,
+            $user
         );
     }
 
@@ -362,11 +339,4 @@ public function mkdir(string $path, ?string $user = null): string
     {
         return $this->server->ssh($user)->exec('mkdir -p '.$path);
     }
-
-    private function deleteTempFile(string $name): void
-    {
-        if (Storage::disk('local')->exists($name)) {
-            Storage::disk('local')->delete($name);
-        }
-    }
 }
diff --git a/app/SSH/Services/PHP/PHP.php b/app/SSH/Services/PHP/PHP.php
index 2c71a4c1..43ff8031 100644
--- a/app/SSH/Services/PHP/PHP.php
+++ b/app/SSH/Services/PHP/PHP.php
@@ -135,7 +135,7 @@ public function createFpmPool(string $user, string $version, $site_id): void
                 'user' => $user,
                 'version' => $version,
             ]),
-            true
+            'root'
         );
 
         $this->service->server->systemd()->restart($this->service->unit);
diff --git a/app/SSH/Services/ProcessManager/Supervisor.php b/app/SSH/Services/ProcessManager/Supervisor.php
index c2d81d25..c1b6e69b 100644
--- a/app/SSH/Services/ProcessManager/Supervisor.php
+++ b/app/SSH/Services/ProcessManager/Supervisor.php
@@ -55,7 +55,7 @@ public function create(
                 'numprocs' => (string) $numprocs,
                 'logFile' => $logFile,
             ]),
-            true
+            'root'
         );
 
         $this->service->server->ssh()->exec(
diff --git a/app/SSH/Services/Webserver/Nginx.php b/app/SSH/Services/Webserver/Nginx.php
index 3c477de3..4c6f0dd8 100755
--- a/app/SSH/Services/Webserver/Nginx.php
+++ b/app/SSH/Services/Webserver/Nginx.php
@@ -26,7 +26,7 @@ public function install(): void
             view('ssh.services.webserver.nginx.nginx', [
                 'user' => $this->service->server->getSshUser(),
             ]),
-            true
+            'root'
         );
 
         $this->service->server->systemd()->restart('nginx');
@@ -83,7 +83,7 @@ public function createVHost(Site $site): void
             view('ssh.services.webserver.nginx.vhost', [
                 'site' => $site,
             ]),
-            true
+            'root'
         );
 
         $this->service->server->ssh()->exec(
@@ -108,7 +108,7 @@ public function updateVHost(Site $site, ?string $vhost = null): void
             $vhost ?? view('ssh.services.webserver.nginx.vhost', [
                 'site' => $site,
             ]),
-            true
+            'root'
         );
 
         $this->service->server->systemd()->restart('nginx');
diff --git a/app/Support/Testing/SSHFake.php b/app/Support/Testing/SSHFake.php
index 5973d8f3..3d5c4a32 100644
--- a/app/Support/Testing/SSHFake.php
+++ b/app/Support/Testing/SSHFake.php
@@ -82,7 +82,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
         return $output;
     }
 
-    public function upload(string $local, string $remote): void
+    public function upload(string $local, string $remote, ?string $owner = null): void
     {
         $this->uploadedLocalPath = $local;
         $this->uploadedRemotePath = $remote;
diff --git a/app/Web/Pages/Servers/FileManager/Index.php b/app/Web/Pages/Servers/FileManager/Index.php
index a5f94126..ddad7554 100644
--- a/app/Web/Pages/Servers/FileManager/Index.php
+++ b/app/Web/Pages/Servers/FileManager/Index.php
@@ -14,7 +14,7 @@ class Index extends Page
 
     public function mount(): void
     {
-        $this->authorize('update', $this->server);
+        $this->authorize('manage', $this->server);
     }
 
     public function getWidgets(): array
diff --git a/app/Web/Pages/Servers/FileManager/Widgets/FilesList.php b/app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
index 244900d4..11c33100 100644
--- a/app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
+++ b/app/Web/Pages/Servers/FileManager/Widgets/FilesList.php
@@ -269,9 +269,10 @@ protected function uploadAction(): Action
             ->after(function (array $data) {
                 run_action($this, function () use ($data) {
                     foreach ($data['file'] as $file) {
-                        $this->server->ssh($this->serverUser)->upload(
+                        $this->server->ssh()->upload(
                             Storage::disk('tmp')->path($file),
                             $this->path.'/'.$file,
+                            $this->serverUser
                         );
                     }
                     $this->refresh();
diff --git a/app/Web/Pages/Servers/Page.php b/app/Web/Pages/Servers/Page.php
index 9b380777..42c0cccc 100644
--- a/app/Web/Pages/Servers/Page.php
+++ b/app/Web/Pages/Servers/Page.php
@@ -60,7 +60,7 @@ public function getSubNavigation(): array
                 ->url(DatabasesIndex::getUrl(parameters: ['server' => $this->server]));
         }
 
-        if (auth()->user()->can('update', $this->server)) {
+        if (auth()->user()->can('manage', $this->server)) {
             $items[] = NavigationItem::make(FileManagerIndex::getNavigationLabel())
                 ->icon('heroicon-o-folder')
                 ->isActiveWhen(fn () => request()->routeIs(FileManagerIndex::getRouteName().'*'))
diff --git a/docker/publish.sh b/docker/publish.sh
new file mode 100644
index 00000000..23a5564d
--- /dev/null
+++ b/docker/publish.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+TAG=$1
+
+if [ -z "$TAG" ]; then
+    echo "No tag provided"
+    exit 1
+fi
+
+rm -rf /tmp/vito
+
+git clone git@github.com:vitodeploy/vito.git /tmp/vito
+
+cd /tmp/vito || exit
+
+docker buildx build . \
+    -f docker/Dockerfile \
+    -t vitodeploy/vito:"$TAG" \
+    --platform linux/amd64,linux/arm64 \
+    --no-cache \
+    --push