diff --git a/Changelog.md b/Changelog.md
index eb225ea43394204c6cb89bfca1e143d9facfb252..195abb735acc5245076d3ce57346980bf73611bc 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,8 @@
 
 ## Refactor
 
+* Make setting up a development environment 9001% easier by adding a Docker-based setup [#7870](https://github.com/diaspora/diaspora/pull/7870)
+
 ## Bug fixes
 
 ## Features
diff --git a/docker/develop/Dockerfile b/docker/develop/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..cf51b3fb88a9e11f9fc8c38b3b40aa72cc2234d4
--- /dev/null
+++ b/docker/develop/Dockerfile
@@ -0,0 +1,62 @@
+FROM ruby:2.4.4-slim-stretch
+
+RUN DEBIAN_FRONTEND=noninteractive \
+    apt-get update && \
+    apt-get install -y -qq \
+        build-essential \
+        cmake \
+        curl \
+        ghostscript \
+        git \
+        imagemagick \
+        libcurl4-openssl-dev \
+        libidn11-dev \
+        libmagickwand-dev \
+        libmariadbclient-dev \
+        libpq-dev \
+        libssl-dev \
+        libxml2-dev \
+        libxslt-dev \
+        nodejs \
+        gosu \
+    && \
+    rm -rf /var/lib/apt/lists/*
+
+
+ARG DIA_UID
+ARG DIA_GID
+
+ENV HOME="/home/diaspora" \
+    GEM_HOME="/diaspora/vendor/bundle"
+
+RUN addgroup --gid $DIA_GID diaspora && \
+    adduser \
+        --no-create-home \
+        --disabled-password \
+        --gecos "" \
+        --uid $DIA_UID \
+        --gid $DIA_GID \
+        diaspora \
+    && \
+    mkdir $HOME /diaspora && \
+    chown -R diaspora:diaspora $HOME /diaspora
+
+
+RUN curl -L \
+        https://cifiles.diasporafoundation.org/phantomjs-2.1.1-linux-x86_64.tar.bz2 \
+        | tar -xj -C /usr/local/bin \
+            --transform='s#.*/##' \
+            phantomjs-2.1.1-linux-x86_64/bin/phantomjs
+
+
+ENV BUNDLE_PATH="$GEM_HOME" \
+    BUNDLE_BIN="$GEM_HOME/bin" \
+    BUNDLE_APP_CONFIG="/diaspora/.bundle"
+ENV PATH $BUNDLE_BIN:$PATH
+
+
+COPY docker-entrypoint.sh /entrypoint.sh
+COPY docker-exec-entrypoint.sh /exec-entrypoint.sh
+ENTRYPOINT ["/entrypoint.sh"]
+
+CMD ["./script/server"]
diff --git a/docker/develop/docker-compose.yml b/docker/develop/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..83fbc83fbdf5f2ddfcb1159dc82c489a0cb579a3
--- /dev/null
+++ b/docker/develop/docker-compose.yml
@@ -0,0 +1,41 @@
+version: "3.4"
+
+volumes:
+  postgresql_data:
+  mysql_data:
+  dia_data_tmp:
+  dia_data_bundle:
+
+services:
+  diaspora:
+    build:
+      context: .
+      dockerfile: Dockerfile
+      args:
+        DIA_UID: "${DIASPORA_ROOT_UID}"
+        DIA_GID: "${DIASPORA_ROOT_GID}"
+    image: diaspora:dev-latest
+    volumes:
+      - "${DIASPORA_ROOT}:/diaspora:rw"
+      - dia_data_tmp:/diaspora/tmp
+      - dia_data_bundle:/diaspora/vendor/bundle
+    ports:
+      - 8080:3000
+    depends_on:
+      - "${DIASPORA_DOCKER_DB}"
+
+  postgresql:
+    image: postgres:10.3
+    ports:
+      - 55432:5432
+    volumes:
+      - postgresql_data:/var/lib/postgresql
+
+  mysql:
+    image: mariadb:10.2
+    ports:
+      - 53306:3306
+    volumes:
+      - mysql_data:/var/lib/mysql
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
diff --git a/docker/develop/docker-entrypoint.sh b/docker/develop/docker-entrypoint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..19f9cdb57ec739827f849b3cd925fb2f6a00ceef
--- /dev/null
+++ b/docker/develop/docker-entrypoint.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+# ----- Ensure correct ownership of /diaspora -----
+dia_home=/home/diaspora
+
+HOST_UID=$(stat -c %u /diaspora)
+HOST_GID=$(stat -c %g /diaspora)
+
+if ! getent group $HOST_GID >/dev/null; then
+  groupmod --gid $HOST_GID diaspora
+fi
+
+if ! getent passwd $HOST_UID >/dev/null; then
+  usermod --uid $HOST_UID --gid $HOST_GID diaspora
+fi
+
+chown -R $HOST_UID:$HOST_GID /home/diaspora
+mkdir -p /diaspora/tmp/pids
+chown $HOST_UID:$HOST_GID /diaspora/tmp /diaspora/tmp/pids /diaspora/vendor/bundle
+
+# ----- Wait for DB ----
+if [ -z $DIA_NODB ] || [ ! $DIA_NODB -eq 1 ]; then
+  if grep -qFx "  <<: *postgresql" /diaspora/config/database.yml; then
+    host=postgresql
+    port=5432
+  else
+    host=mysql
+    port=3306
+  fi
+
+  c=0
+
+  trap '{ exit 1; }' INT
+  while ! (< /dev/tcp/${host}/${port}) 2>/dev/null; do
+    printf "\rWaiting for $host:$port to become ready ... ${c}s"
+    sleep 1
+    ((c++))
+  done
+  trap - INT
+  if [ ! -z $c ]; then
+    printf "\rWaiting for $host:$port to become ready ... done (${c}s)\n"
+  fi
+fi
+
+cd /diaspora
+
+gosu $HOST_UID:$HOST_GID "$@"
diff --git a/docker/develop/docker-exec-entrypoint.sh b/docker/develop/docker-exec-entrypoint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..547f11cb9d42eceb5bcc3c68094c627cc37fd04f
--- /dev/null
+++ b/docker/develop/docker-exec-entrypoint.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+HOST_UID=$(stat -c %u /diaspora)
+HOST_GID=$(stat -c %g /diaspora)
+
+cd /diaspora
+gosu $HOST_UID:$HOST_GID "$@"
diff --git a/script/diaspora-dev b/script/diaspora-dev
new file mode 100755
index 0000000000000000000000000000000000000000..7a2f51e60e5f6995c3d5fffd28741fa2ea2092ca
--- /dev/null
+++ b/script/diaspora-dev
@@ -0,0 +1,554 @@
+#!/bin/bash
+
+
+# ----- Usage information -----
+
+print_usage() {
+  # Print help for the first argument
+  case "$1" in
+  # management
+    setup)
+      echo; echo "Set up the environment for diaspora*"
+      echo; echo "This command is an alias for the execution of the commands"
+      echo "build, config, bundle, migrate and setup-tests, in that order."
+      print_usage_header "setup [options]" \
+        "    --force    Rebuild image without using Docker's cache;" \
+        "               overwrite existing configuration" \
+        "    --mysql    Use MySQL as database (PostgreSQL is default)"
+      ;;
+    start)
+      echo; echo "Start diaspora* (includes database)"
+      print_usage_header "start [options]" \
+        "-d    Run in background"
+      ;;
+    stop)
+      echo; echo "Stop diaspora* (includes database)"
+      print_usage_header "stop"
+      ;;
+    restart)
+      echo; echo "Restart diaspora* using bin/eye (fast restart)"
+      print_usage_header "restart [options]" \
+        "    --full    Restart entire container"
+      ;;
+    logs)
+      echo; echo "Follow log output of the running diaspora* instance"
+      print_usage_header "logs [options]" \
+        "-a, --all     Follow all containers, including databases"
+      ;;
+    status)
+      echo; echo "Show currently running diaspora* Docker container(s) and related image(s)"
+      print_usage_header "status"
+      ;;
+    clean)
+      echo; echo "Delete diaspora* Docker containers and volumes (includes database)"
+      print_usage_header "clean [options]" \
+        "    --config    Delete configuration files as well"
+      ;;
+  # test & development
+    cucumber)
+      echo; echo "Run cucumber tests"
+      echo; echo "The specified cucumber tests will be executed. If none are given, all"
+      echo "tests are executed."
+      print_usage_header "cucumber [TEST...]"
+      ;;
+    jasmine)
+      echo; echo "Run all jasmine tests"
+      print_usage_header "jasmine"
+      ;;
+    rspec)
+      echo; echo "Run rspec tests"
+      echo; echo  "The specified rspec tests will be executed. If none are given, all"
+      echo "tests will be executed."
+      print_usage_header "rspec"
+      ;;
+    pronto)
+      echo; echo "Run pronto checks"
+      print_usage_header "pronto"
+      ;;
+    migrate)
+      echo; echo "Execute pending migrations (incl. database setup)"
+      print_usage_header "migrate [options]" \
+        "-d    Run in background"
+      ;;
+  # misc
+    build)
+      echo; echo "(Re-)Build Docker image diaspora:dev-latest"
+      print_usage_header "build [options]" \
+        "    --no-cache    Rebuild image without using Docker's cache"
+      ;;
+    bundle)
+      echo; echo "Install gems using bundle into $DIASPORA_ROOT"
+      print_usage_header "bundle [options]" \
+        "-d    Run in background"
+      ;;
+    config)
+      echo; echo "Create basic configuration files for usage with PostgreSQL (default)"
+      print_usage_header "config [options]" \
+        "    --mysql       Use MySQL as database (PostgreSQL is default)" \
+        "    --overwrite   Overwrite existing configuration"
+      ;;
+    exec)
+      echo; echo "Execute a command in a diaspora* Docker container"
+      echo; echo "If there is no running diaspora* Docker container, a new one is created"
+      echo "and started."
+      print_usage_header "exec [options] COMMAND [ARGS...]" \
+        "-d    Run in background"
+      ;;
+    help)
+      echo; echo "Show help on a command"
+      print_usage_header "help COMMAND"
+      ;;
+    setup-tests)
+      echo; echo "Prepare cached files and database contents for tests"
+      print_usage_header "setup-tests"
+      ;;
+    *)
+      print_usage_full
+      ;;
+  esac
+}
+
+print_usage_header() {
+  # Print formatted usage information for COMMAND
+  # Usage: print_usage_header COMMAND [FLAG_DESCRIPTION...]
+  echo; echo "Usage:  $1"
+  shift
+  if [ $# -gt 0 ]; then
+    echo; echo "Options:"
+    while [ $# -gt 0 ]; do
+      echo "  $1"
+      shift
+    done
+  fi
+}
+
+print_usage_full() {
+  # Print overview of available commands
+  # $SCRIPT_NAME [help|-h|--help] leads here
+  echo; echo "Setup and run a diaspora instance for development in no time."
+  print_usage_header "$SCRIPT_NAME COMMAND"
+  echo
+  echo "Management Commands:"
+  echo "  setup         Prepare diaspora* to run for development"
+  echo "  start         Start diaspora*"
+  echo "  stop          Stop diaspora*"
+  echo "  restart       Restart of diaspora*"
+  echo "  logs          Follow log output of diaspora*"
+  echo "  status        Show current instance status of diaspora*"
+  echo "  clean         Reset diaspora* instance"
+  echo
+  echo "Test and Development Commands:"
+  echo "  cucumber      Run cucumber tests"
+  echo "  jasmine       Run jasmine tests"
+  echo "  rspec         Run rspec tests"
+  echo "  pronto        Run pronto checks"
+  echo "  migrate       Execute pending migrations"
+  echo
+  echo "Misc. Commands:"
+  echo "  build         Build basic diaspora* environment"
+  echo "  bundle        (Re-)Install gems for diaspora*"
+  echo "  config        Configure diaspora*"
+  echo "  exec          Execute a command in the run environment (advanced)"
+  echo "  help          Show help for commands"
+  echo "  setup-tests   Prepare diaspora* test environment"
+  echo
+  echo "Run '$SCRIPT_NAME help COMMAND' for more information on a command."
+}
+
+
+# ----- Helper functions -----
+
+dia_fetch_upstream() {
+  # Add and fetch upstream develop branch
+  if ! git remote show | grep -q '^upstream$'; then
+    git remote add upstream https://github.com/diaspora/diaspora.git
+  fi
+  git fetch upstream develop
+}
+
+dia_is_configured() {
+  # Check if config files exist
+  [ -f "$DIASPORA_CONFIG_DB" ] && [ -f "$DIASPORA_CONFIG_DIA" ]
+}
+
+exit_if_unconfigured() {
+  # Exit if config does not seem complete
+  if ! dia_is_configured; then
+    echo "Fatal: Config files missing. Run the 'setup' or 'config' command to configure."
+    exit 1
+  fi
+}
+
+dia_is_running() {
+  # Check if diaspora container is running
+  docker-compose ps --services --filter status=running | grep -qx 'diaspora'
+}
+
+dia_is_db_running() {
+  # Check if db container is running
+  docker-compose ps --services --filter status=running | grep -qx $DIASPORA_DOCKER_DB
+}
+
+dia_get_db() {
+  # Get currently configured or assumed db type
+  grep -q '^  <<: \*mysql' "$DIASPORA_CONFIG_DB" 2>/dev/null && echo mysql || echo postgresql
+}
+
+# ----- Command functions -----
+
+dia_build() {
+  if [ $# -gt 0 ] && [ "$1" == "--no-cache" ]; then nocache="--no-cache"; fi
+  # Build the diaspora Docker container (diaspora:dev-latest)
+  docker-compose build $nocache diaspora
+}
+
+dia_bundle() {
+  # Run bundle in order to install all gems into $DIASPORA_ROOT
+  # Do not start database, not required and sometimes not yet configured
+  echo "Installing gems via bundler ..."
+  docker-compose run \
+    --rm \
+    --no-deps $1 \
+    -e DIA_NODB=1 \
+    diaspora \
+    /bin/sh -c "gem install bundler && script/configure_bundler && bin/bundle install --full-index"
+}
+
+dia_clean() {
+  # Delete all containers and volumes
+  for i in "$@"; do
+    case "$i" in
+      --config)
+        dia_config_delete=1
+        ;;
+    esac
+  done
+  docker-compose down -v
+  if [ ! -z $dia_config_delete ]; then
+    rm "$DIASPORA_CONFIG_DIA" "$DIASPORA_CONFIG_DB"
+  fi
+}
+
+dia_config() {
+  # Create rudimentary configuration files if they don't exist
+  echo "Configuring diaspora ..."
+  for i in "$@"; do
+    case "$i" in
+      --mysql)
+        dia_config_mysql=1
+        ;;
+      --overwrite)
+        dia_config_delete=1
+        ;;
+    esac
+  done
+  [ ! -f "$DIASPORA_ROOT"/public/source.tar.gz ] && touch "$DIASPORA_ROOT"/public/source.tar.gz
+  # Delete existing files if requested
+  if [ ! -z $dia_config_delete ]; then
+    rm "$DIASPORA_CONFIG_DIA" "$DIASPORA_CONFIG_DB"
+  fi
+  # Create new diaspora.yml if none exists
+  if [ ! -f "$DIASPORA_CONFIG_DIA" ]; then
+    cp "$DIASPORA_CONFIG_DIA".example "$DIASPORA_CONFIG_DIA"
+  fi
+  # Select database type
+  if [ -z $dia_config_mysql ]; then
+    export DIASPORA_DOCKER_DB=postgresql
+  else
+    export DIASPORA_DOCKER_DB=mysql
+  fi
+  # Create new database.yml if none exists
+  if [ ! -f "$DIASPORA_CONFIG_DB" ]; then
+    sed -E '
+      /^postgresql/,/^[[:alpha:]]/ {
+        s/host:.*/host: postgresql/
+        s/password.*/password: postgres/
+      }
+      /^mysql/,/^[[:alpha:]]/ {
+        s/host:.*/host: mysql/
+        s/password:.*/password: mysql/
+      }
+      /^common/,/^[[:alpha:]]/ {
+        s/^(\s+<<:).*/\1 *'$DIASPORA_DOCKER_DB'/
+      }' "$DIASPORA_CONFIG_DB".example > "$DIASPORA_CONFIG_DB"
+  fi
+  # Update exisiting database.yml to reflect correct database type
+  if [ "$(dia_get_db)" != "$DIASPORA_DOCKER_DB" ]; then
+    sed -E -i'' '
+      /^common/,/^[[:alpha:]]/ {
+        s/^(\s+<<:).*/\1 *'$DIASPORA_DOCKER_DB'/
+      }' "$DIASPORA_CONFIG_DB"
+  fi
+}
+
+dia_cucumber() {
+  # Run cucumber tests
+  if [ "$1" == "-d" ]; then detach="-d"; shift; fi
+  docker-compose run \
+    --rm $detach \
+    diaspora \
+    bin/cucumber "$@"
+}
+
+dia_exec() {
+  # Run a custom command inside a running diaspora container. Start a new one if necessary.
+  exit_if_unconfigured
+  if [ "$1" == "-d" ]; then detach="-d"; shift; fi
+  if dia_is_running; then
+    # Use a running container
+    docker-compose exec $detach diaspora /exec-entrypoint.sh "$@"
+  else
+    if ! dia_is_db_running; then not_running=1; fi
+    # Start a new container
+    echo "No running instance found, starting new one for command execution ..."
+    docker-compose run --rm $detach --service-ports diaspora "$@"
+    if [ ! -z $not_running ]; then
+      docker-compose stop $DIASPORA_DOCKER_DB
+    fi
+  fi
+}
+
+dia_jasmine() {
+  # Run jasmine tests
+  docker-compose run \
+    --rm $1 \
+    -e RAILS_ENV=test \
+    diaspora \
+    bin/rake jasmine:ci
+}
+
+dia_logs() {
+  # Show logs of running diaspora* instance
+  dia_follow=diaspora
+  for i in "$@"; do
+    case "$i" in
+      -a|--all)
+        dia_follow=""
+        ;;
+    esac
+  done
+  docker-compose logs -f --tail=100 $dia_follow
+}
+
+dia_migrate() {
+  # Run migrations if configured
+  echo "Creating and/or migrating database ..."
+  exit_if_unconfigured
+  docker-compose run \
+    --rm $1 \
+    diaspora \
+    bin/rake db:create db:migrate
+}
+
+dia_pronto() {
+  # Run pronto checks
+  exit_if_unconfigured
+  cd "$DIASPORA_ROOT"
+  if git diff-index --quiet HEAD --; then
+    dia_fetch_upstream
+  fi
+  cd - >/dev/null
+  docker-compose run \
+    --rm \
+    --no-deps \
+    diaspora \
+    bin/pronto run --unstaged -c upstream/develop
+}
+
+dia_restart() {
+  # Restart diaspora inside container if already running; start new container otherwise
+  for i in "$@"; do
+    case "$i" in
+      --full)
+        dia_restart_full=1
+        ;;
+    esac
+  done
+  if dia_is_running; then
+    if [ -z $dia_restart_full ]; then
+      docker-compose exec \
+        diaspora \
+        bin/eye restart diaspora
+    else
+      docker-compose restart
+    fi
+  else
+    dia_start
+  fi
+}
+
+dia_rspec() {
+  # Run rspec tests
+  exit_if_unconfigured
+  assets=""
+  # Assumption: If (and only if) the tested file is not available, assets need be regenerated
+  [ -f "$DIASPORA_ROOT"/public/404.html ] && assets="assets:generate_error_pages"
+  # Prepare database (and assets if necessary)
+  docker-compose run \
+    --rm \
+    -e RAILS_ENV=test \
+    diaspora \
+    bin/rake db:create db:migrate $assets
+  # Run tests
+  docker-compose run \
+    --rm \
+    diaspora \
+    bin/rspec "$@"
+}
+
+dia_setup() {
+  for i in "$@"; do
+    case "$i" in
+      --force)
+        build="$build --no-cache"
+        config="$config --overwrite"
+        ;;
+      --mysql)
+        config="$config --mysql"
+        ;;
+    esac
+  done
+  # Prepare the entire environment for development
+  (
+    set -e
+    dia_build $build
+    dia_config $config
+    dia_bundle $bundle
+    dia_migrate $migrate
+    dia_setup_tests $setup_tests
+  )
+  # stop db afterwards as it is not needed while dia is not running
+  docker-compose stop $DIASPORA_DOCKER_DB
+}
+
+dia_setup_tests() {
+  # Prepare all possible tests
+  # stop db if it was not running before
+  echo "Setting up environment for tests ..."
+  if ! dia_is_db_running; then stopdb="docker-compose stop $DIASPORA_DOCKER_DB"; fi
+  docker-compose run \
+    --rm \
+    -e RAILS_ENV=test \
+    diaspora \
+    bin/rake db:create db:migrate tests:generate_fixtures assets:generate_error_pages
+  $stopdb
+}
+
+dia_start() {
+  # Start all containers if config appears to exist
+  exit_if_unconfigured
+  if [ $# -eq 0 ]; then
+    options=--abort-on-container-exit
+  else
+    options=$1
+  fi
+  docker-compose up $options diaspora
+}
+
+dia_status() {
+  # Print running containers and current images
+  docker-compose ps
+  echo
+  docker-compose images
+}
+
+dia_stop() {
+  # Stop all containers
+  docker-compose stop
+}
+
+
+# ----- Variables -----
+# Symlinks are treated as files
+export SCRIPT_NAME=$(basename "${BASH_SOURCE[0]}")
+export SCRIPT_ROOT=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# Assumption: The script is in the "script" subfolder of the diaspora root folder
+export DIASPORA_ROOT=$(dirname "$SCRIPT_ROOT")
+export DIASPORA_ROOT_UID=1001
+export DIASPORA_ROOT_GID=1001
+export DIASPORA_CONFIG_DIA=$DIASPORA_ROOT/config/diaspora.yml
+export DIASPORA_CONFIG_DB=$DIASPORA_ROOT/config/database.yml
+export DIASPORA_DOCKER_DB=$(dia_get_db)
+
+export COMPOSE_FILE=$DIASPORA_ROOT/docker/develop/docker-compose.yml
+export COMPOSE_PROJECT_NAME=diasporadev
+
+# ----- Arg parsing -----
+if [ $# -lt 1 ]; then
+  print_usage
+  exit 1
+fi
+
+dia_command=$1
+shift
+
+case "$dia_command" in
+  --help|-h)
+    print_usage_full
+    exit 0
+    ;;
+  help)
+    if [ $# -lt 1 ]; then
+      print_usage_full
+    else
+      print_usage "$1"
+    fi
+    exit 0
+    ;;
+  build)
+    dia_build "$@"
+    ;;
+  bundle)
+    dia_bundle "$1"
+    ;;
+  clean)
+    dia_clean "$@"
+    ;;
+  config)
+    dia_config "$@"
+    ;;
+  cucumber)
+    dia_cucumber "$@"
+    ;;
+  exec)
+    dia_exec "$@"
+    ;;
+  jasmine)
+    dia_jasmine
+    ;;
+  logs)
+    dia_logs "$@"
+    ;;
+  migrate)
+    dia_migrate "$@"
+    ;;
+  pronto)
+    dia_pronto
+    ;;
+  restart)
+    dia_restart "$@"
+    ;;
+  rspec)
+    dia_rspec "$@"
+    ;;
+  setup)
+    dia_setup "$@"
+    ;;
+  setup-tests)
+    dia_setup_tests
+    ;;
+  start)
+    dia_start "$1"
+    ;;
+  status)
+    dia_status
+    ;;
+  stop)
+    dia_stop
+    ;;
+  *)
+    print_usage
+    exit 1
+    ;;
+esac