diff --git a/README.md b/README.md
index 846c1e3a40c1e6c5c8d7bb544de33a1a04b08195..20708934bbec13a963237ba302c8f16421091b2d 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,36 @@
 The original SSH executor can only be used for a single/static host described
 in the runner config. This is not as flexible as our use-case's needs.
 
-# Sources
+# Install
+```sh
+cd /opt
+git clone git@git.sch.bme.hu:kszk/opensource/ssh-executor.git ssh-executor
+cd ssh-executor
+./install/executor.sh
+```
+
+Generate an SSH key for the root user. This will be used for the authentication of the ssh executor.
+
+Setup a runner in the `/etc/gitlab-runner/config.toml` file like the following:
+```yaml
+[[runners]]
+  name = "kszk-deploy"
+  url = "https://git.sch.bme.hu/"
+  token = "**********"
+  executor = "custom"
+  builds_dir = "/tmp/kszk-deploy/builds" # remote deploy/repo dir (ssh)
+  cache_dir = "/tmp/kszk-deploy/cache"  # remote cache dir (ssh)
+  [runners.custom_build_dir]
+  [runners.cache]
+    [runners.cache.s3]
+    [runners.cache.gcs]
+  [runners.custom]
+    prepare_exec = "/opt/ssh-executor/prepare.sh" # suctom executor script
+    run_exec = "/opt/ssh-executor/run.sh" # suctom executor script
+```
+
+Profit.
+
+# Sources/credits
 - <https://docs.gitlab.com/runner/executors/ssh.html>
 - <https://docs.gitlab.com/runner/executors/custom_examples/lxd.html>
diff --git a/install_executor.sh b/install_executor.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4ee62ff6d382fe3a1af1f12b28967e23a8c8222f
--- /dev/null
+++ b/install_executor.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+#!/usr/bin/env bash
+
+cp ssh_config /root/.ssh/config
+chown -R root:root .
+chmod 664 /root/.ssh/config
diff --git a/prepare.sh b/prepare.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d48e262c30e4a3768e2d5d50b93037d6faf6dbb0
--- /dev/null
+++ b/prepare.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+# this will make your script exit if any command in a pipeline errors.
+set -eo pipefail
+
+# trap any error, and mark it as a system failure.
+trap "exit $SYSTEM_FAILURE_EXIT_CODE" ERR
+
+if [ -z "$CUSTOM_ENV_SSH_HOST" ]; then
+    echo "SSH_HOST variable is NOT present. Please provide it in the .gitlab-ci.yml file in the variables directive."
+    exit 1
+fi
+
+SSH_HOST=$CUSTOM_ENV_SSH_HOST
+SSH_USER=${CUSTOM_ENV_SSH_USER:=kszk-gitlab-deployer}
+SSH_PORT=${CUSTOM_ENV_SSH_PORT:=22}
+
+# This will run the script generated by GitLab Runner
+SSH_CMD="ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -T"
+
+echo "Prepare SSH command: $SSH_CMD"
+echo "$SSH_CMD" > ssh_command
diff --git a/run.sh b/run.sh
index e9fef3aad944447c6b488397d12840c8ae1f1dc2..1c8911a5f37c0cebf22295202c8d6a08838535a7 100755
--- a/run.sh
+++ b/run.sh
@@ -1,19 +1,44 @@
 #!/usr/bin/env bash
 
-JOB_ID="runner-$CUSTOM_ENV_CI_RUNNER_ID-project-$CUSTOM_ENV_CI_PROJECT_ID-concurrent-$CUSTOM_ENV_CI_CONCURRENT_PROJECT_ID-$CUSTOM_ENV_CI_JOB_ID"
+# This script will be executed MULTIPLE times
+# see: https://docs.gitlab.com/runner/executors/custom.html#run
 
+# Two arguments:
+# $1: The path to the script that GitLab Runner creates for the Custom executor to run.
+# $2: Name of the stage.
+SCRIPT_PATH="$1"
+STAGE="$2"
+
+# Sub stages (in sequential order):
+# - prepare_script
+# - get_sources
+# - restore_cache
+# - download_artifacts
+# - step_*
+# - build_script
+# - step_*
+# - after_script
+# - archive_cache
+# - upload_artifacts_on_success OR upload_artifacts_on_failure
+
+# this will make your script exit if any command in a pipeline errors.
 set -eo pipefail
 
 # trap any error, and mark it as a system failure.
-trap "exit $SYSTEM_FAILURE_EXIT_CODE" ERR
+trap "echo FAIL_HERE; exit $SYSTEM_FAILURE_EXIT_CODE" ERR
+
+# debug sub-stages:
+#echo "Running run.sh with args: $*"
 
-echo "Running in $JOB_ID"
+# see: prepare.sh
+SSH_CMD=$(cat ssh_command)
 
-# This will run the script generated by GitLab Runner by sending the
-# content of the script to the container via STDIN.
+# This will run the script generated by GitLab Runner on the remote host
+# leave exit 0 at the end, it causes exit 1 with the ssh command
+head -n -1 "${1}" | $SSH_CMD
 
-cat < "${1}"
 if [ $? -ne 0 ]; then
     # Exit using the variable, to make the build as failure in GitLab CI.
     exit $BUILD_FAILURE_EXIT_CODE
 fi
+exit
diff --git a/ssh_config b/ssh_config
new file mode 100644
index 0000000000000000000000000000000000000000..9ca668b372446d61cf9cd63e436b65a179e83096
--- /dev/null
+++ b/ssh_config
@@ -0,0 +1,13 @@
+### DO NOT EDIT, it will be overwriten
+### DO NOT EDIT, it will be overwriten
+### DO NOT EDIT, it will be overwriten
+# Edit only in repo: https://git.sch.bme.hu/kszk/opensource/ssh-executor
+
+Host *
+  KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
+  MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com
+  Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
+  ServerAliveInterval 10
+  ControlMaster auto
+  ControlPersist yes
+  ControlPath ~/.ssh/socket-%r@%h:%p