diff --git a/docker-compose.yml b/docker-compose.yml
index ff62842..970c42d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -37,8 +37,8 @@ services:
       DEPLOYMENT_NAME: "CAcert.org Website (local development)"
       MYSQL_WEBDB_HOSTNAME: db
       MYSQL_WEBDB_DATABASE: cacert
-      CSR_DIRECTORY: /csr
-      CRT_DIRECTORY: /crt
+      CSR_DIRECTORY: /certs/csr
+      CRT_DIRECTORY: /certs/crt
       DEFAULT_HOSTNAME: www.cacert.localhost
       SECURE_HOSTNAME: secure.cacert.localhost
       TVERIFY_HOSTNAME: tverify.cacert.localhost
@@ -56,6 +56,7 @@ services:
       - smtp
     volumes:
       - ./cacert-software:/www
+      - certstaging:/certs
   mgr:
     build:
       context: .
@@ -85,7 +86,36 @@ services:
       - db
     volumes:
       - ./cacert-cats:/var/www/cats
+  signer_client:
+    build:
+      context: .
+      dockerfile: signer_client.Dockerfile
+    env_file:
+      - ./.env
+    environment:
+      MYSQL_WEBDB_HOSTNAME: db
+      MYSQL_WEBDB_DATABASE: cacert
+      CSR_DIRECTORY: /srv/certs/csr
+      CRT_DIRECTORY: /srv/certs/crt
+    volumes:
+      - certstaging:/srv/certs
+      - signersockets:/srv/sockets
+    depends_on:
+      - db
+  signer:
+    build:
+      context: .
+      dockerfile: signer.Dockerfile
+    environment:
+      SIGNER_WORKDIR: /srv/ca/work
+      SIGNER_CA_CONFIG: /srv/caconfig
+    volumes:
+      - signersockets:/srv/sockets
+      - signerdata:/srv/ca
 
 volumes:
   db: { }
-  maildir: { }
\ No newline at end of file
+  maildir: { }
+  certstaging: { }
+  signersockets: { }
+  signerdata: { }
\ No newline at end of file
diff --git a/docker/run-signer b/docker/run-signer
new file mode 100755
index 0000000..95a0487
--- /dev/null
+++ b/docker/run-signer
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -eu
+
+rm -f /srv/sockets/signer
+socat -d -d PTY,link=/dev/ttyUSB0 UNIX-LISTEN:/srv/sockets/signer 2>&1 &
+sleep 1
+
+export SERIAL_PORT=/dev/ttyUSB0
+
+cd /srv/CommModule/
+
+touch server.pl-active
+exec perl -w server.pl
\ No newline at end of file
diff --git a/docker/run-signer_client b/docker/run-signer_client
new file mode 100755
index 0000000..7c19dae
--- /dev/null
+++ b/docker/run-signer_client
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+set -eu
+
+socat -d -d UNIX:/srv/sockets/signer PTY,link=/dev/ttyS0 2>&1 &
+sleep 1
+
+export SERIAL_PORT=/dev/ttyS0
+
+cd /srv/CommModule/
+
+touch client.pl-active
+exec perl -w client.pl
diff --git a/docker/signer-config/class3-client.cnf b/docker/signer-config/class3-client.cnf
new file mode 100644
index 0000000..d2b25d6
--- /dev/null
+++ b/docker/signer-config/class3-client.cnf
@@ -0,0 +1,39 @@
+[ ca ]
+default_ca             = CA_default         # The default ca section
+
+[ CA_default ]
+dir                    = /srv/ca/class3      # Where everything is kept
+certs                  = $dir/certs          # Where the issued certs are kept
+crl_dir                = $dir/crl            # Where the issued crl are kept
+crlnumber              = $dir/crlnumber      # bug-1438
+database               = $dir/index.txt      # database index file.
+new_certs_dir          = $dir/newcerts       # default place for new certs.
+certificate            = $dir/ca.crt.pem     # The CA certificate
+serial                 = $dir/serial         # The current serial number
+crl                    = $dir/crl.pem        # The current CRL
+private_key            = $dir/ca.key.pem     # The private key
+RANDFILE               = $dir/private/.rand  # private random number file
+x509_extensions        = usr_cert            # The extentions to add to the cert
+default_days           = 200                 # how long to certify for
+default_crl_days       = 30                  # how long before next CRL
+default_md             = sha512              # which md to use.
+preserve               = no                  # keep passed DN ordering
+policy                 = policy_anything
+
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = optional
+emailAddress           = optional
+
+[ usr_cert ]
+basicConstraints       = critical, CA:FALSE
+nsComment              = "To get your own certificate for FREE head over to http://www.CAcert.org"
+keyUsage               = critical, digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage       = emailProtection, clientAuth, msEFS, msSGC, nsSGC
+authorityInfoAccess    = OCSP;URI:http://ocsp.cacert.org
+crlDistributionPoints  = URI:http://crl.cacert.localhost/class3-revoke.crl
+subjectAltName         = email:copy
diff --git a/docker/signer-config/openssl-client.cnf b/docker/signer-config/openssl-client.cnf
new file mode 100644
index 0000000..ec2a975
--- /dev/null
+++ b/docker/signer-config/openssl-client.cnf
@@ -0,0 +1,39 @@
+[ ca ]
+default_ca             = CA_default         # The default ca section
+
+[ CA_default ]
+dir                    = /srv/ca/CA          # Where everything is kept
+certs                  = $dir/certs          # Where the issued certs are kept
+crl_dir                = $dir/crl            # Where the issued crl are kept
+crlnumber              = $dir/crlnumber      # bug-1438
+database               = $dir/index.txt      # database index file.
+new_certs_dir          = $dir/newcerts       # default place for new certs.
+certificate            = $dir/ca.crt.pem     # The CA certificate
+serial                 = $dir/serial         # The current serial number
+crl                    = $dir/crl.pem        # The current CRL
+private_key            = $dir/ca.key.pem     # The private key
+RANDFILE               = $dir/private/.rand  # private random number file
+x509_extensions        = usr_cert            # The extentions to add to the cert
+default_days           = 200                 # how long to certify for
+default_crl_days       = 30                  # how long before next CRL
+default_md             = sha512              # which md to use.
+preserve               = no                  # keep passed DN ordering
+policy                 = policy_anything
+
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = optional
+emailAddress           = optional
+
+[ usr_cert ]
+basicConstraints       = critical, CA:FALSE
+nsComment              = "To get your own certificate for FREE head over to http://www.CAcert.org"
+keyUsage               = critical, digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage       = emailProtection, clientAuth, msEFS, msSGC, nsSGC
+authorityInfoAccess    = OCSP;URI:http://ocsp.cacert.org
+crlDistributionPoints  = URI:http://crl.cacert.localhost/revoke.crl
+subjectAltName         = email:copy
diff --git a/signer.Dockerfile b/signer.Dockerfile
new file mode 100644
index 0000000..989702f
--- /dev/null
+++ b/signer.Dockerfile
@@ -0,0 +1,24 @@
+FROM debian:jessie
+
+RUN apt-get update \
+    && DEBIAN_FRONTEND=noninteractive \
+    apt-get install -y --no-install-recommends \
+    gnupg \
+    libdevice-serialport-perl \
+    libdigest-sha-perl \
+    libfile-counterfile-perl \
+    openssl \
+    perl \
+    socat \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/*
+
+VOLUME /srv/ca
+
+COPY cacert-software/CommModule/server.pl \
+     cacert-software/CommModule/logclean.sh \
+     /srv/CommModule/
+COPY docker/run-signer usr/local/bin/
+COPY docker/signer-config/* /srv/caconfig/
+
+CMD ["/usr/local/bin/run-signer"]
diff --git a/signer_client.Dockerfile b/signer_client.Dockerfile
new file mode 100644
index 0000000..af0d786
--- /dev/null
+++ b/signer_client.Dockerfile
@@ -0,0 +1,26 @@
+FROM debian:jessie
+
+RUN apt-get update \
+    && DEBIAN_FRONTEND=noninteractive \
+    apt-get install -y --no-install-recommends \
+    gnupg \
+    libdbd-mysql-perl \
+    libdbi-perl \
+    libdevice-serialport-perl \
+    libfile-counterfile-perl \
+    openssl \
+    perl \
+    socat \
+    && apt-get clean \
+    && rm -rf /var/lib/apt/lists/*
+
+VOLUME /srv/certs
+
+COPY cacert-software/CommModule/client.pl \
+     cacert-software/CommModule/logclean.sh \
+     /srv/CommModule/
+COPY docker/run-signer_client usr/local/bin/
+
+WORKDIR /srv/CommModule
+
+CMD ["run-signer_client"]
diff --git a/webdb.Dockerfile b/webdb.Dockerfile
index 0df244a..7cf73c5 100644
--- a/webdb.Dockerfile
+++ b/webdb.Dockerfile
@@ -52,6 +52,7 @@ COPY docker/php5-cacert.ini /etc/php5/mods-available/cacert.ini
 COPY docker/feed.rss /usr/local/etc/application/feed.rss
 
 VOLUME /www
+VOLUME /certs
 
 RUN a2ensite www.cacert.localhost ; \
     a2dissite 000-default ; \