diff --git a/.gitignore b/.gitignore
index 5dc4571d8501c1ec7f1aaae4d00036c5f07ed64c..4bebf3fd047902df6d0d129ddfe157bdcf769321 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,7 @@ eslint-report.html
 /config/redis.shared_state.yml
 /config/unicorn.rb
 /config/puma.rb
+/config/puma_actioncable.rb
 /config/secrets.yml
 /config/sidekiq.yml
 /config/registry.key
diff --git a/bin/actioncable b/bin/actioncable
new file mode 100755
index 0000000000000000000000000000000000000000..0aacb19e070cd061b1a74a49887a1ee7bfc0f7d6
--- /dev/null
+++ b/bin/actioncable
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+set -e
+
+cd $(dirname $0)/..
+app_root=$(pwd)
+
+puma_pidfile="$app_root/tmp/pids/puma_actioncable.pid"
+puma_config="$app_root/config/puma_actioncable.rb"
+
+spawn_puma()
+{
+  exec bundle exec puma --config "${puma_config}" --environment "$RAILS_ENV" "$@"
+}
+
+get_puma_pid()
+{
+  pid=$(cat "${puma_pidfile}")
+  if [ -z "$pid" ] ; then
+    echo "Could not find a PID in $puma_pidfile"
+    exit 1
+  fi
+  echo "${pid}"
+}
+
+start()
+{
+  spawn_puma -d
+}
+
+start_foreground()
+{
+  spawn_puma
+}
+
+stop()
+{
+  get_puma_pid
+  kill -QUIT "$(get_puma_pid)"
+}
+
+reload()
+{
+  kill -USR2 "$(get_puma_pid)"
+}
+
+case "$1" in
+  start)
+    start
+    ;;
+  start_foreground)
+    start_foreground
+    ;;
+  stop)
+    stop
+    ;;
+  reload)
+    reload
+    ;;
+  *)
+    echo "Usage: RAILS_ENV=your_env $0 {start|start_foreground|stop|reload}"
+    ;;
+esac
diff --git a/cable/config.ru b/cable/config.ru
new file mode 100644
index 0000000000000000000000000000000000000000..3b93c483ded1c1d4bf93fc53c656fb3101746e34
--- /dev/null
+++ b/cable/config.ru
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require ::File.expand_path('../../config/environment', __FILE__)
+Rails.application.eager_load!
+
+run ActionCable.server
diff --git a/config/application.rb b/config/application.rb
index ab104a1d97bc67e7f9b79886376be561119da756..14e92bf5905dca23a7ce942e155fc3073a5f5244 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -8,6 +8,7 @@
 require 'action_controller/railtie'
 require 'action_view/railtie'
 require 'action_mailer/railtie'
+require 'action_cable/engine'
 require 'rails/test_unit/railtie'
 
 Bundler.require(*Rails.groups)
diff --git a/config/initializers/actioncable.rb b/config/initializers/actioncable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ed96f965150a5969bc5f364af1b8a5d188bb2091
--- /dev/null
+++ b/config/initializers/actioncable.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+Rails.application.configure do
+  # Prevents the default engine from being mounted because
+  # we're running ActionCable as a standalone server
+  config.action_cable.mount_path = nil
+  config.action_cable.url = Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/cable')
+end
diff --git a/config/puma_actioncable.example.development.rb b/config/puma_actioncable.example.development.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aef15da54f9a85a75fedb6f118e574a949580732
--- /dev/null
+++ b/config/puma_actioncable.example.development.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+# -----------------------------------------------------------------------
+# This file is used by the GDK to generate a default config/puma_actioncable.rb file
+# Note that `/home/git` will be substituted for the actual GDK root
+# directory when this file is generated
+# -----------------------------------------------------------------------
+
+# Load "path" as a rackup file.
+#
+# The default is "cable/config.ru".
+#
+rackup 'cable/config.ru'
+pidfile '/home/git/gitlab/tmp/pids/puma_actioncable.pid'
+state_path '/home/git/gitlab/tmp/pids/puma_actioncable.state'
+
+## Uncomment the lines if you would like to write puma stdout & stderr streams
+## to a different location than rails logs.
+## When using GitLab Development Kit, by default, these logs will be consumed
+## by runit and can be accessed using `gdk tail rails-actioncable`
+# stdout_redirect '/home/git/gitlab/log/puma_actioncable.stdout.log',
+#  '/home/git/gitlab/log/puma_actioncable.stderr.log',
+#  true
+
+# Configure "min" to be the minimum number of threads to use to answer
+# requests and "max" the maximum.
+#
+# The default is "0, 16".
+#
+threads 1, 4
+
+# By default, workers accept all requests and queue them to pass to handlers.
+# When false, workers accept the number of simultaneous requests configured.
+#
+# Queueing requests generally improves performance, but can cause deadlocks if
+# the app is waiting on a request to itself. See https://github.com/puma/puma/issues/612
+#
+# When set to false this may require a reverse proxy to handle slow clients and
+# queue requests before they reach puma. This is due to disabling HTTP keepalive
+queue_requests false
+
+# Bind the server to "url". "tcp://", "unix://" and "ssl://" are the only
+# accepted protocols.
+bind 'unix:///home/git/gitlab_actioncable.socket'
+
+workers 2
+
+require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
+
+on_restart do
+  # Signal application hooks that we're about to restart
+  Gitlab::Cluster::LifecycleEvents.do_before_master_restart
+end
+
+before_fork do
+  # Signal to the puma killer
+  Gitlab::Cluster::PumaWorkerKillerInitializer.start @config.options unless ENV['DISABLE_PUMA_WORKER_KILLER']
+
+  # Signal application hooks that we're about to fork
+  Gitlab::Cluster::LifecycleEvents.do_before_fork
+end
+
+Gitlab::Cluster::LifecycleEvents.set_puma_options @config.options
+on_worker_boot do
+  # Signal application hooks of worker start
+  Gitlab::Cluster::LifecycleEvents.do_worker_start
+end
+
+# Preload the application before starting the workers; this conflicts with
+# phased restart feature. (off by default)
+
+preload_app!
+
+tag 'gitlab-actioncable-puma-worker'
+
+# Verifies that all workers have checked in to the master process within
+# the given timeout. If not the worker process will be restarted. Default
+# value is 60 seconds.
+#
+worker_timeout 60
+
+# Use json formatter
+require_relative "/home/git/gitlab/lib/gitlab/puma_logging/json_formatter"
+
+json_formatter = Gitlab::PumaLogging::JSONFormatter.new
+log_formatter do |str|
+  json_formatter.call(str)
+end