diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 741bab5a8c..b05804aa16 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,6 +5,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
 ;;; Copyright © 2020 Jesse Dowell <jessedowell@gmail.com>
 ;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
+;;; Copyright © 2022 Maya Tomasek <maya.omase@disroot.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -37,6 +38,8 @@ (define-module (gnu services docker)
 
   #:export (docker-configuration
             docker-service-type
+            docker-container-configuration
+            docker-container-service-type
             singularity-service-type))
 
 (define-configuration docker-configuration
@@ -164,6 +167,198 @@ (define docker-service-type
                                      (const %docker-accounts))))
                 (default-value (docker-configuration))))
 
+(define (pair-of-strings? val)
+  (and (pair val)
+       (string? (car val))
+       (string? (cdr val))))
+
+(define (list-of-pair-of-strings? val)
+  (list-of pair-of-strings?))
+
+(define-configuration/no-serialization docker-container-configuration
+  (name
+   (symbol '())
+   "Name of the docker container. Will be used to denote service to Shepherd and must be unique!
+We recommend, that the name of the container is prefixed with @code{docker-}.")
+  (comment
+   (string "")
+   "A documentation on the docker container.")
+  (image-name
+   (string)
+   "A name of the image that will be used. (Note that the existence of the image
+is not guaranteed by this daemon.)")
+  (volumes
+   (list-of-pair-of-strings '())
+   "A list of volume binds. In (HOST_PATH CONTAINER_PATH) format.")
+  (ports
+   (list-of-pair-of-strings '())
+   "A list of port binds. In (HOST_PORT CONTAINER_PORT) or (HOST_PORT CONTAINER_PORT OPTIONS) format.
+For example, both port bindings are valid:
+
+@lisp
+(ports '((\"2222\" \"22\") (\"21\" \"21\" \"tcp\")))
+@end lisp"
+   (environments
+    (list-of-pair-of-strings '())
+    "A list of variable binds, inside the container enviornment. In (VARIABLE VALUE) format."))
+  (network
+   (string "none")
+   "Network type.")
+  (additional-arguments
+   (list-of-strings '())
+   "Additional arguments to the docker command line interface.")
+  (container-command
+   (list-of-strings '())
+   "Command to send into the container.")
+  (attached?
+   (boolean #t)
+   "Run the container as an normal attached process (sending SIGTERM).
+Or run the container as a isolated environment that must be stopped with @code{docker stop}.
+
+Please verify first, that you container is indeed not attached, otherwise @code{shepherd} might
+assume the process is dead, even when it is not.
+
+You can do that, by first running your container with @code{docker run image-name}.
+
+Then check @code{docker ps}, if the command shows beside your container the word @code{running}.
+Your container is indeed detached, but if it shows @code{starting}, and it doesn't flip to
+@code{running} after a while, it means that you container is attached, and you need to keep this
+option turned @code{#t}."))
+
+(define (serialize-volumes config)
+  "Serialize list of pairs into flat list of @code{(\"-v\" \"HOST_PATH:CONTAINER_PATH\" ...)}"
+  (append-map
+   (lambda (volume-bind)
+     (list "-v" (format #f "~?" "~a:~a" volume-bind)))
+   (docker-container-configuration-volumes config)))
+
+(define (serialize-ports config)
+  "Serialize list of either pairs, or lists into flat list of
+@code{(\"-p\" \"NUMBER:NUMBER\" \"-p\" \"NUMBER:NUMBER/PROTOCOL\" ...)}"
+  (append-map
+   (lambda (port-bind)
+     (list "-p" (format #f "~?" "~a:~a~^/~a" port-bind)))
+   (docker-container-configuration-ports config)))
+
+(define (serialized-environments config)
+  "Serialize list of pairs into flat list of @code{(\"-e\" \"VAR=val\" \"-e\" \"VAR=val\" ...)}."
+  (append-map
+   (lambda (env-bind)
+     (list "-e" (format #f "~?" "~a=~a" env-bind)))
+   (docker-container-configuration-environments config)))
+
+(define (docker-container-startup-script docker-cli container-name config)
+  "Return a program file, that executes the startup sequence of the @code{docker-container-shepherd-service}."
+  (let* ((attached? (docker-container-configuration-attached? config))
+         (image-name (docker-container-configuration-image config))
+         (volumes (serialize-volumes config))
+         (ports (serialize-ports config))
+         (envs (serialize-environments config))
+         (network (docker-container-configuration-network config))
+         (additional-arguments (docker-container-configuration-additional-arguments config))
+         (container-command (docker-container-configuration-container-command config)))
+    (program-file
+     (string-append "start-" container-name "-container")
+     #~(let ((docker (string-append #$docker-cli "/bin/docker")))
+         (system* docker "stop" #$container-name)
+         (system* docker "rm" #$container-name)
+         (apply system* `(,docker
+                          "run"
+                          ,(string-append "--name=" #$container-name)
+                          ;; Automatically remove the container when stopping
+                          ;; If you want persistent data, you need to use
+                          ;; volume binds or other methods.
+                          "--rm"
+                          ,(string-append "--network=" #$network)
+                          ;; TODO:
+                          ;; Write to a cid file the container id, this allows
+                          ;; for shepherd to manage container even when the process
+                          ;; itself gets detached from the container
+                          ,@(if (not #$attached) '("--cidfile" #$cid-file) '())
+                          #$@volumes
+                          #$@ports
+                          #$@envs
+                          #$@additional-arguments
+                          ,#$image-name
+                          #$@container-command))))))
+
+(define (docker-container-shepherd-service docker-cli config)
+  "Return a shepherd-service that runs CONTAINER."
+  (let* ((container-name (symbol->string (docker-container-configuration-name config)))
+         (cid-file (string-append "/var/run/docker/" container-name ".pid"))
+         (attached? (docker-container-configuration-attached? config)))
+    (shepherd-service
+     (provision (list (docker-container-configuration-name config)))
+     (requirement `(dockerd))
+     (start #~(make-forkexec-constructor
+               (list #$(docker-container-startup-script docker-cli container-name config))
+               ;; Watch the cid-file instead of the docker run command, as the daemon can
+               ;; still be running even when the command terminates
+               (if (not #$attached?)
+                   #:pid-file #$cid-file)))
+     (stop (if #$attached?
+               #~(make-kill-destructor)
+               #~(lambda _
+                   (exec-command (list
+                                  (string-append #$docker-cli "/bin/docker")
+                                  "stop" #$container-name))
+                   #f))))))
+
+
+(define (list-of-docker-container-configurations? val)
+  (list-of docker-container-configuration?))
+
+(define-configuration/no-serialization docker-container-service-configuration
+  (docker-cli
+   (file-like docker-cli)
+   "The docker package to use.")
+  (containers
+   (list-of-docker-container-configurations '())
+   "The docker containers to run."))
+
+(define (docker-container-shepherd-services config)
+  "Return shepherd services for all containers inside config."
+  (let ((docker-cli (docker-container-service-configuration-docker-cli config)))
+    (map
+     (lambda (container)
+       (docker-container-shepherd-service
+        docker-cli
+        container))
+     (docker-container-service-configuration-containers config))))
+
+(define docker-container-service-type
+  "This is the type of the service that runs docker containers using GNU Shepherd.
+It allows for declarative management of running containers inside the Guix System.
+
+This service can be extended with list of @code{<docker-container-configuration>} objects.
+
+The following is an example @code{docker-container-service-type} configuration.
+
+@lisp
+(service docker-container-service-type
+  (containers (list
+                (docker-container-configuration
+                  (name 'docker-example)
+                  (comment \"An example docker container configuration\")
+                  (image-name \"example/example:latest\") ;; Note that images must be provided separately.
+                  (volumes '((\"/mnt\" \"/\") (\"/home/example\" \"/home/user\")))
+                  (ports '((\"21\" \"21\" \"tcp\") (\"22\" \"22\")))
+                  (network \"host\")))))
+@end lisp"
+  (service-type
+   (name 'docker-container)
+   (description "Manage docker containers with shepherd.")
+   (extensions
+    (list (service-extension shepherd-root-service-type docker-container-shepherd-services)))
+   (compose concatenate)
+   (extend (lambda (config containers)
+             (let ((docker-cli (docker-container-service-configuration-docker-cli config))
+                   (initial-containers (docker-container-service-configuration-containers config)))
+               (docker-container-service-configuration
+                (docker-cli docker-cli)
+                (containers (append initial-containers containers))))))
+   (default-value (docker-container-service-configuration))))
+
 
 ;;;
 ;;; Singularity.
