diff --git a/.gitignore b/.gitignore
index 8750fb4f7..70d847cfc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*.phar
phpunit.xml
.phpunit.result.cache
+docker-compose.override.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b79b3df2..283f81f9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
- Added labels and selectors support.
- Added support for placeholders in `run()` func.
- Added support for secret passing in `run()` func without outputting to logs.
+- Added docker-based E2E testing environment. [#2197]
### Changed
- Refactored executor engine, up to 2x faster than before.
@@ -596,6 +597,7 @@
- Fixed remove of shared dir on first deploy.
+[#2197]: https://github.com/deployphp/deployer/issues/2197
[#1994]: https://github.com/deployphp/deployer/issues/1994
[#1990]: https://github.com/deployphp/deployer/issues/1990
[#1989]: https://github.com/deployphp/deployer/issues/1989
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 000000000..0fd355a5a
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,82 @@
+FROM php:7.3-cli-alpine AS composer
+RUN apk add wget
+COPY ./scripts/install-composer.sh /tmp/install-composer.sh
+RUN sh /tmp/install-composer.sh
+
+
+
+
+
+FROM php:7.3-cli-alpine AS deployer
+RUN apk add \
+ git \
+ openssh-client \
+ rsync
+
+RUN ssh-keygen \
+ -q \
+ -b 2048 \
+ -t rsa \
+ -f ~/.ssh/id_rsa
+
+ARG XDEBUG_VERSION=2.9.8
+RUN set -eux; \
+ apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \
+ pecl install xdebug-$XDEBUG_VERSION; \
+ docker-php-ext-enable xdebug; \
+ apk del .build-deps
+
+COPY --from=composer /tmp/composer /bin/composer
+ENV E2E_ENV=1
+VOLUME [ "/project" ]
+WORKDIR /project
+
+
+
+
+
+FROM php:7.3-apache AS server
+RUN apt-get update && apt-get install -y \
+ git \
+ openssh-server \
+ sudo \
+ && rm -rf /var/lib/apt/lists/*
+
+# SSH login fix. Otherwise user is kicked off after login
+RUN mkdir /run/sshd \
+ && sed -i 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' /etc/pam.d/sshd
+
+# Configure Apache to expose healthcheck & configure site to use /var/www/html/current ad document root
+COPY ./conf/healthcheck.conf /etc/apache2/sites-available/healthcheck.conf
+COPY ./initial-site/index.html /var/www/html/initial-site/index.html
+
+ENV APACHE_DOCUMENT_ROOT /var/www/html/current
+RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/000-default.conf \
+ && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
+ && ln -s /var/www/html/initial-site /var/www/html/current \
+ && chown -R www-data:www-data /var/www/html \
+ && echo "Listen 81" >> /etc/apache2/ports.conf \
+ && a2enmod rewrite \
+ && a2ensite healthcheck
+
+RUN useradd \
+ --create-home \
+ deployer \
+ && echo 'deployer:deployer' | chpasswd \
+ && echo 'deployer ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
+ && mkdir ~deployer/.ssh \
+ && touch ~deployer/.ssh/authorized_keys \
+ && chown -R deployer:deployer ~deployer/.ssh \
+ && chmod 700 ~deployer/.ssh \
+ && chmod 600 ~deployer/.ssh/authorized_keys
+
+COPY ./scripts/start-servers.sh /usr/local/bin/start-servers
+COPY --from=composer /tmp/composer /usr/local/bin/composer
+COPY --from=deployer /root/.ssh/id_rsa.pub /tmp/root_rsa.pub
+
+RUN chmod a+x /usr/local/bin/start-servers \
+ && cat /tmp/root_rsa.pub >> ~deployer/.ssh/authorized_keys \
+ && rm -rf /tmp/root_rsa.pub
+
+EXPOSE 22 80 81
+CMD [ "start-servers" ]
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 000000000..1b323ba3c
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,88 @@
+# Deployer E2E testing environment
+
+This directory contains an end-to-end testing environment for Deployer.
+
+All commands mentioned in this readme, should be executed in the `docker` directory.
+
+## Requirements
+
+* Docker
+* docker-compose
+
+## Running tests
+
+The E2E are started when running the `docker-compose up` command.
+This will start the `server` container that has the Apache, OpenSSH & PHP 7.3 enabled.
+
+Once the `server` is up and running, the `deployer` container will be started and alongside it
+the tests will be ran.
+
+## Adding new E2E tests
+
+The E2E test should be a part of the `e2e` test suite.
+Each `e2e` test class should inherit from `AbstractE2ETest` class.
+
+Note: E2E tests will only run in an environment where env variable `E2E_ENV` has been set and has a truthy value.
+
+## Manually accessing the `deployer` container.
+
+The container can be accessed by running:
+
+```bash
+docker-compose run deployer sh
+```
+
+This command will spawn a `sh` shell inside the `deployer` container.
+
+## About containers
+
+### `deployer` container
+
+The `deployer` container contains:
+
+* git
+* PHP 7.3 with XDebug enabled
+* rsync
+* SSH client
+
+It is possible to access the `server` container via ssh by running:
+
+```bash
+ssh deployer@server
+```
+
+`root`'s public key has been added to authorized keys for `deployer` user.
+
+#### Enabling XDebug
+
+To enable XDebug create a `docker-compose.override.yml` file with following content:
+
+```dockerfile
+services:
+ deployer:
+ environment:
+ # See https://docs.docker.com/docker-for-mac/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host
+ # See https://github.com/docker/for-linux/issues/264
+ # The `remote_host` below may optionally be replaced with `remote_connect_back=1`
+ XDEBUG_CONFIG: >-
+ remote_enable=1
+ remote_host=${XDEBUG_HOST:-host.docker.internal}
+ remote_autostart=1
+ remote_port=9000
+ idekey=PHPSTORM
+ # This should correspond to the server declared in PHPStorm `Preferences | Languages & Frameworks | PHP | Servers`
+ # Then PHPStorm will use the corresponding path mappings
+ PHP_IDE_CONFIG: serverName=deployer-e2e
+```
+
+Note: you may want to set the `XDEBUG_HOST` env variable to point to your IP address when running tests in Linux.
+
+### `server` container
+
+The `server` container contains:
+
+* Apache (with the `DocumentRoot` set to `/var/www/html/current`)
+* git
+* PHP 7.3
+* SSH server with
+* sudo (user `deployer` can use `sudo` without having to enter passwords)
diff --git a/docker/conf/healthcheck.conf b/docker/conf/healthcheck.conf
new file mode 100644
index 000000000..f51410f86
--- /dev/null
+++ b/docker/conf/healthcheck.conf
@@ -0,0 +1,32 @@
+