diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..45884c46ffeadaab73213baf482e02627b69be21
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/.idea
+*.iml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0f6f3d8a85feaa3b89ef4631ba205aba0571b351
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,27 @@
+daemon:
+  stage: build
+  image:
+    name: gcr.io/kaniko-project/executor:debug
+    entrypoint: [ "" ]
+  script:
+    - mkdir -p /kaniko/.docker
+    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
+    - /kaniko/executor --context $CI_PROJECT_DIR/daemon --dockerfile $CI_PROJECT_DIR/daemon/Dockerfile --destination $CI_REGISTRY_IMAGE:daemon-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:daemon
+wireguard:
+  stage: build
+  image:
+    name: gcr.io/kaniko-project/executor:debug
+    entrypoint: [ "" ]
+  script:
+    - mkdir -p /kaniko/.docker
+    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
+    - /kaniko/executor --context $CI_PROJECT_DIR/wireguard --dockerfile $CI_PROJECT_DIR/wireguard/Dockerfile --destination $CI_REGISTRY_IMAGE:wireguard-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:wireguard
+init:
+  stage: build
+  image:
+    name: gcr.io/kaniko-project/executor:debug
+    entrypoint: [ "" ]
+  script:
+    - mkdir -p /kaniko/.docker
+    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
+    - /kaniko/executor --context $CI_PROJECT_DIR/init --dockerfile $CI_PROJECT_DIR/init/Dockerfile --destination $CI_REGISTRY_IMAGE:init-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:init
diff --git a/daemon/Dockerfile b/daemon/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..57ed402d9707ecc5b7a41dd0f590c7a96e5dc936
--- /dev/null
+++ b/daemon/Dockerfile
@@ -0,0 +1,83 @@
+FROM alpine:3.15 AS builder
+ARG RTORRENT_VERSION=0.9.8
+ARG LIBTORRENT_VERSION=0.13.8
+RUN apk add --no-cache \
+    alpine-sdk \
+    autoconf \
+    automake \
+    curl-dev \
+    libsigc++-dev \
+    libtool \
+    linux-headers \
+    ncurses-dev \
+    openssl-dev \
+    xmlrpc-c-dev \
+    zlib-dev
+
+RUN git clone --branch v${RTORRENT_VERSION} --single-branch https://github.com/rakshasa/rtorrent.git /rtorrent
+RUN git clone --branch v${LIBTORRENT_VERSION} --single-branch https://github.com/rakshasa/libtorrent.git /libtorrent
+
+WORKDIR /libtorrent
+RUN ./autogen.sh && \
+    ./configure \
+        --prefix=/usr \
+        --disable-debug \
+        --disable-instrumentation && \
+    make -j$(nproc) && \
+    mkdir -p /build && \
+    DESTDIR=/build make install && \
+    make install
+
+WORKDIR /rtorrent
+RUN ./autogen.sh && \
+    ./configure \
+        --prefix=/usr \
+        --disable-debug \
+        --with-xmlrpc-c && \
+    make -j$(nproc) && \
+    mkdir -p /build && \
+    DESTDIR=/build make install
+
+FROM alpine:3.15
+
+RUN apk add --no-cache \
+    curl \
+    libgcc \
+    libstdc++ \
+    musl \
+    ncurses-libs \
+    xmlrpc-c
+
+COPY --from=builder /build/usr/bin/rtorrent /usr/bin/rtorrent
+COPY --from=builder /build/usr/lib/libtorrent.so* /usr/lib/
+
+ENV RT_TRACKER_UDP="yes"
+ENV RT_DHT_MODE="disable"
+ENV RT_DHT_PORT=49160
+ENV RT_PROTO_PEX="no"
+ENV RT_MAX_UP=100
+ENV RT_MAX_UP_GLOBAL=250
+ENV RT_MIN_PEERS=20
+ENV RT_MAX_PEERS=60
+ENV RT_MIN_PEERS_SEED=30
+ENV RT_MAX_PEERS_SEED=80
+ENV RT_TRACKERS_WANT=80
+ENV RT_MEMORY_MAX="1800M"
+ENV RT_DAEMON="true"
+ENV RT_XMLRPC_BIND="0.0.0.0"
+ENV RT_XMLRPC_PORT="5000"
+
+RUN adduser -u 1000 -D rtorrent && \
+    addgroup rtorrent rtorrent && \
+    mkdir /data && chown rtorrent /data && \
+    mkdir /private && chown rtorrent /private
+
+USER rtorrent
+
+COPY src/rtorrent.conf /rtorrent.conf
+COPY entrypoint.sh /entrypoint.sh
+
+VOLUME ["/data"]
+VOLUME ["/tmp"]
+
+CMD ["/entrypoint.sh"]
diff --git a/daemon/entrypoint.sh b/daemon/entrypoint.sh
new file mode 100644
index 0000000000000000000000000000000000000000..dc50890bb0a4f507c3685c619f0881949fd79f89
--- /dev/null
+++ b/daemon/entrypoint.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -euo pipefail
+
+rm /tmp/rtorrent.log
+touch /tmp/rtorrent.log
+rm /data/.session/rtorrent.lock /data/.session/rtorrent.pid
+
+# Start rtorrent as a daemon with the setting in the config file
+# & to actually run it in the background
+rtorrent -n -o "import=/rtorrent.conf" &
+
+tail -f /tmp/rtorrent.log
diff --git a/daemon/src/rtorrent.conf b/daemon/src/rtorrent.conf
new file mode 100644
index 0000000000000000000000000000000000000000..83547cac1871b596c50d7a253933b8205289733e
--- /dev/null
+++ b/daemon/src/rtorrent.conf
@@ -0,0 +1,124 @@
+#############################################################################
+# A minimal rTorrent configuration that provides the basic features
+# you want to have in addition to the built-in defaults.
+#
+# See https://github.com/rakshasa/rtorrent/wiki/CONFIG-Template
+# for an up-to-date version.
+#############################################################################
+
+
+## Instance layout (base paths)
+method.insert = cfg.basedir,  private|const|string, (cat,(system.env,RT_BASE_DIR))
+method.insert = cfg.tmpdir,  private|const|string, (cat,"/tmp/")
+method.insert = cfg.download, private|const|string, (cat,(cfg.basedir),"download/")
+method.insert = cfg.logs,     private|const|string, (cat,(cfg.tmpdir),"log/")
+method.insert = cfg.logfile,  private|const|string, (cat,(cfg.logs),"rtorrent.log")
+method.insert = cfg.session,  private|const|string, (cat,(cfg.basedir),".session/")
+method.insert = cfg.watch,    private|const|string, (cat,(cfg.basedir),"watch/")
+
+
+## Create instance directories
+execute.throw = sh, -c, (cat,\
+    "mkdir -p \"",(cfg.download),"\" ",\
+    "\"",(cfg.logs),"\" ",\
+    "\"",(cfg.session),"\" ",\
+    "\"",(cfg.watch),"/load\" ",\
+    "\"",(cfg.watch),"/start\" ")
+
+
+## Listening port for incoming peer traffic (fixed; you can also randomize it)
+network.port_range.set = 50000-50000
+network.port_random.set = no
+
+
+## Tracker-less torrent and UDP tracker support
+## (conservative settings for 'private' trackers, change for 'public')
+dht.mode.set = (system.env,RT_DHT_MODE)
+#dht.port.set = (system.env,RT_DHT_PORT)
+protocol.pex.set = (system.env,RT_PROTO_PEX)
+
+trackers.use_udp.set = (system.env,RT_TRACKER_UDP)
+
+
+## Peer settings
+throttle.max_uploads.set = (system.env,RT_MAX_UP)
+throttle.max_uploads.global.set = (system.env,RT_MAX_UP_GLOBAL)
+throttle.min_peers.normal.set = (system.env,RT_MIN_PEERS)
+throttle.max_peers.normal.set = (system.env,RT_MAX_PEERS)
+throttle.min_peers.seed.set = (system.env,RT_MIN_PEERS_SEED)
+throttle.max_peers.seed.set = (system.env,RT_MAX_PEERS_SEED)
+trackers.numwant.set = (system.env,RT_TRACKERS_WANT)
+
+protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
+
+
+## Limits for file handle resources, this is optimized for
+## an `ulimit` of 1024 (a common default). You MUST leave
+## a ceiling of handles reserved for rTorrent's internal needs!
+network.http.max_open.set = 50
+network.max_open_files.set = 600
+network.max_open_sockets.set = 300
+
+
+## Memory resource usage (increase if you have a large number of items loaded,
+## and/or the available resources to spend)
+pieces.memory.max.set = (system.env,RT_MEMORY_MAX)
+network.xmlrpc.size_limit.set = 4M
+
+
+## Basic operational settings (no need to change these)
+session.path.set = (cat, (cfg.session))
+directory.default.set = (cat, (cfg.download))
+log.execute = (cat, (cfg.logs), "execute.log")
+#log.xmlrpc = (cat, (cfg.logs), "xmlrpc.log")
+execute.nothrow = sh, -c, (cat, "echo >",\
+    (session.path), "rtorrent.pid", " ",(system.pid))
+
+
+## Other operational settings (check & adapt)
+encoding.add = utf8
+system.umask.set = 0027
+system.cwd.set = (directory.default)
+network.http.dns_cache_timeout.set = 25
+schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
+#pieces.hash.on_completion.set = no
+#view.sort_current = seeding, greater=d.ratio=
+#keys.layout.set = qwerty
+#network.http.capath.set = "/etc/ssl/certs"
+#network.http.ssl_verify_peer.set = 0
+#network.http.ssl_verify_host.set = 0
+
+
+## Some additional values and commands
+method.insert = system.startup_time, value|const, (system.time)
+method.insert = d.data_path, simple,\
+    "if=(d.is_multi_file),\
+        (cat, (d.directory), /),\
+        (cat, (d.directory), /, (d.name))"
+method.insert = d.session_file, simple, "cat=(session.path), (d.hash), .torrent"
+
+
+## Watch directories (add more as you like, but use unique schedule names)
+## Add torrent
+schedule2 = watch_load, 11, 10, ((load.verbose, (cat, (cfg.watch), "load/*.torrent")))
+## Add & download straight away
+schedule2 = watch_start, 10, 10, ((load.start_verbose, (cat, (cfg.watch), "start/*.torrent")))
+
+
+## Run the rTorrent process as a daemon in the background
+## (and control via XMLRPC sockets)
+system.daemon.set = (system.env,RT_DAEMON)
+network.scgi.open_port = (cat,(system.env,RT_XMLRPC_BIND),:,(system.env,RT_XMLRPC_PORT))
+#network.scgi.open_local = (cat,(session.path),rpc.socket)
+#execute.nothrow = chmod,770,(cat,(session.path),rpc.socket)
+
+
+## Logging:
+##   Levels = critical error warn notice info debug
+##   Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_*
+print = (cat, "Logging to ", (cfg.logfile))
+log.open_file = "log", (cfg.logfile)
+log.add_output = "info", "log"
+#log.add_output = "tracker_debug", "log"
+
+### END of rtorrent.rc ###
diff --git a/init/Dockerfile b/init/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..9c1e4fd952e22c488057b7e4de8f94c5718770b3
--- /dev/null
+++ b/init/Dockerfile
@@ -0,0 +1,4 @@
+FROM alpine:3.15
+COPY entrypoint.sh /entrypoint.sh
+ENTRYPOINT ["/entrypoint.sh"]
+
diff --git a/init/entrypoint.sh b/init/entrypoint.sh
new file mode 100644
index 0000000000000000000000000000000000000000..dc3478cf492c069daf64a899f4522383a129ee14
--- /dev/null
+++ b/init/entrypoint.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -euo pipefail
+
+mkdir -p /dev/net
+mknod /dev/net/tun c 10 200
+chmod 0666 /dev/net/tun
diff --git a/wireguard/Dockerfile b/wireguard/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..6b2cbdff251d8ef6059803c0117e81bae69066fe
--- /dev/null
+++ b/wireguard/Dockerfile
@@ -0,0 +1,14 @@
+FROM alpine:3.15
+
+RUN apk add --no-cache \
+    nftables \
+    ip6tables \
+    wireguard-tools
+
+RUN rm /sbin/iptables /sbin/ip6tables
+RUN ln -s /sbin/ip6tables-nft /sbin/ip6tables
+RUN ln -s /sbin/iptables-nft /sbin/iptables
+
+COPY entrypoint.sh /entrypoint.sh
+VOLUME ["/wireguard.conf"]
+CMD ["/entrypoint.sh"]
diff --git a/wireguard/entrypoint.sh b/wireguard/entrypoint.sh
new file mode 100644
index 0000000000000000000000000000000000000000..b8d5e2089cdb843bc62cd8f22fc4ba9841111cc3
--- /dev/null
+++ b/wireguard/entrypoint.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -euo pipefail
+
+wg-quick up "/wireguard.conf"