diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 51e5cc805d2ec89eb2e64fc6ce2e16dc4db5ce82..b9e2f2915f45cb125f4776e73aa46b32663e1ddb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,7 +5,7 @@ test:package:
     - apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev libzip-dev zip
     - apt-get clean
     - pecl install mcrypt inotify
-    - docker-php-ext-enable mcrypt
+    - docker-php-ext-enable mcrypt inotify
     - docker-php-ext-install zip
     - curl --silent --show-error "https://getcomposer.org/installer" | php -- --install-dir=/usr/local/bin --filename=composer
   script:
diff --git a/src/Console/WatchCommand.php b/src/Console/WatchCommand.php
index a313cbb3b8ae518879595f7c0181fe29db7d6865..73674a673306eb62fac45577ca936504d4b6aa24 100644
--- a/src/Console/WatchCommand.php
+++ b/src/Console/WatchCommand.php
@@ -18,6 +18,7 @@ class WatchCommand extends Command
      */
     protected $signature = 'watch:run
                             {--force : Force the worker to run even in maintenance mode}
+                            {--once : Specify if the watcher should stop after one file has been detected}
                             {--sleep=3 : Number of seconds to sleep when no job is available}
                             {--rest=0 : Number of seconds to rest between jobs}
                             {--timeout=0 : Only process the event on the queue}';
@@ -99,7 +100,8 @@ class WatchCommand extends Command
             $this->option('sleep'),
             $this->option('force'),
             $this->option('rest'),
-            $this->option('timeout')
+            $this->option('timeout'),
+            $this->option('once')
         );
     }
 
@@ -107,6 +109,7 @@ class WatchCommand extends Command
      * Listen for the queue events in order to update the console output.
      *
      * @return void
+     * @psalm-suppress UndefinedInterfaceMethod
      */
     protected function listenForEvents(): void
     {
diff --git a/src/Watcher.php b/src/Watcher.php
index 0dae1609ea533fee50ab0a2d3ec6ed7893240466..dc7bc176dae122b607551664bf2337072fd56e46 100644
--- a/src/Watcher.php
+++ b/src/Watcher.php
@@ -48,7 +48,7 @@ class Watcher
     /**
      * The inotify instance
      *
-     * @var resource
+     * @var resource|closed-resource
      */
     protected $inotify;
 
@@ -94,9 +94,9 @@ class Watcher
      * Execute the console command.
      *
      * @param  WatcherOptions  $options
-     * @return int
+     * @return int|null
      */
-    public function daemon(WatcherOptions $options): int
+    public function daemon(WatcherOptions $options)
     {
         if ($this->supportsAsyncSignals()) {
             $this->listenForSignals();
@@ -138,6 +138,10 @@ class Watcher
                         $cookie
                     );
 
+                    if ($options->once) {
+                        $this->shouldQuit = true;
+                    }
+
                     if ($options->rest > 0) {
                         $this->sleep($options->rest);
                     }
@@ -270,11 +274,11 @@ class Watcher
     protected function stopWatchers(): void
     {
         foreach (array_keys($this->watchers) as $watcher) {
-            inotify_rm_watch($this->inotify, $watcher);
+            if(inotify_rm_watch($this->inotify, $watcher)) {
+                unset($this->watchers[$watcher]);
+            }
         }
 
-        $this->watchers = [];
-
         $this->unregisterWatchers();
     }
 
diff --git a/src/WatcherOptions.php b/src/WatcherOptions.php
index 68e000076bf056336f5fc75d1087bfe389e3e7fa..f3a8717e6630ea807d496402d6bc4f6d24a27681 100644
--- a/src/WatcherOptions.php
+++ b/src/WatcherOptions.php
@@ -26,12 +26,19 @@ class WatcherOptions
     public $force;
 
     /**
-     * Indicates if the watcher should stop after one event.
+     * Indicates if the watcher should stop after a specific elapsed time (in seconds).
      *
      * @var mixed
      */
     public $timeout;
 
+    /**
+     * Indicates if the watcher should stop after one event.
+     *
+     * @var mixed
+     */
+    public $once;
+
     /**
      * Create a new watcher options instance.
      *
@@ -40,11 +47,12 @@ class WatcherOptions
      * @param  mixed  $rest
      * @return void
      */
-    public function __construct($sleep = 5, $force = false, $rest = 0, $timeout = 0)
+    public function __construct($sleep = 5, $force = false, $rest = 0, $timeout = 0, $once = false)
     {
         $this->sleep = $sleep;
         $this->rest = $rest;
         $this->force = $force;
         $this->timeout = $timeout;
+        $this->once = $once;
     }
 }
diff --git a/tests/Feature/WatcherCommandTest.php b/tests/Feature/WatcherCommandTest.php
index cdd01878699624d59fcb6a87245cff4a7ebaabda..6ece1ed2eac67eba9e62d7d9bc8c94e01462c1ab 100644
--- a/tests/Feature/WatcherCommandTest.php
+++ b/tests/Feature/WatcherCommandTest.php
@@ -4,7 +4,6 @@ namespace Dterumal\Watcher\Tests\Feature;
 
 use Dterumal\Watcher\Events\FileEvent;
 use Dterumal\Watcher\Events\WatcherCreated;
-use Dterumal\Watcher\Events\WatcherRestarted;
 use Dterumal\Watcher\Events\WatcherStopped;
 use Dterumal\Watcher\Tests\TestCase;
 use Illuminate\Support\Facades\Artisan;
@@ -73,11 +72,53 @@ class WatcherCommandTest extends TestCase
 
         // Check that the watcher was created
         Event::assertDispatched(FileEvent::class, function ($event) {
-            ray($event);
             return $event->directory === 'default' &&
                 $event->mask === 256 &&
                 $event->cookie === 0 &&
                 $event->name === 'test.txt';
         });
     }
+
+    /** @test */
+    function it_detects_a_file_and_exits_with_option_once()
+    {
+        Event::fake();
+
+        // File created
+        $fooFile = storage_path('app/test.txt');
+
+        // make sure we're starting from a clean state
+        if (File::exists($fooFile)) {
+            unlink($fooFile);
+        }
+
+        // Start background process
+        $process = new Process(['/bin/bash', 'sleep-and-create-a-file.sh', $fooFile], $this->getStubDirectory());
+        $process->start();
+
+        // Run the artisan command
+        $exitCode = Artisan::call('watch:run', [
+            '--once' => true
+        ], new NullOutput);
+
+        // Assert a new file is created
+        $this->assertTrue(File::exists($fooFile));
+        unlink($fooFile);
+
+        // Check that exit code
+        $this->assertSame(0, $exitCode);
+
+        // Check that the watcher was created
+        Event::assertDispatched(FileEvent::class, function ($event) {
+            return $event->directory === 'default' &&
+                $event->mask === 256 &&
+                $event->cookie === 0 &&
+                $event->name === 'test.txt';
+        });
+
+        // Check that the watcher was stopped
+        Event::assertDispatched(WatcherStopped::class, function ($event) {
+            return $event->status === 0;
+        });
+    }
 }
\ No newline at end of file