Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TLS secured TCP exposed Docker daemon on Dokku host - setup

TLS secured TCP exposed Docker daemon on Dokku host - setup

  1. Create certificates
  2. Edit Docker options
  3. Restart Docker
  4. Copy client certificates from host
  5. (optional) Add remote endpoint in Portainer

Tested on a standard $5/mo DigitalOcean VPS running Ubuntu 16.04.


Create certificates:

Log into Dokku host as root and create server/client certificates:

export HOST=my.domain
mkdir .docker && cd .docker

openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
openssl genrsa -out server-key.pem 4096
openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
echo subjectAltName = DNS:$HOST,IP:127.0.0.1 >> extfile.cnf
echo extendedKeyUsage = serverAuth >> extfile.cnf
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
openssl genrsa -out key.pem 4096
openssl req -subj '/CN=client' -new -key key.pem -out client.csr
echo extendedKeyUsage = clientAuth >> extfile.cnf
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf
rm client.csr server.csr
chmod 0400 ca-key.pem key.pem server-key.pem
chmod 0444 ca.pem server-cert.pem cert.pem

(please comment if you know how this can be done easier 😭)
For more details refer to the documentation

Edit Docker options:

systemctl edit docker

Add:

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 --tlsverify --tlscacert /root/.docker/ca.pem --tlscert /root/.docker/server-cert.pem --tlskey /root/.docker/server-key.pem -H unix:///var/run/docker.sock

(note: -H unix:///var/run/docker.sock as last argument!)
Creates /etc/systemd/system/docker.service.d/.#override.conf3aa8ba90533acd64
For more details refer to the documentation

Restart Docker:

systemctl restart docker

Copy certificates from host:

Exit host: exit
SFTP into host: sftp root@host

cd .docker
get ca.pem
get cert.pem
get key.pem

Guard these keys like root passwords, malicious users can severely damage your server with access to the Docker daemon.

Add endpoint in Portainer

Provide my.domain:2376, enable TLS and add ca.pem, cert.pem and key.pem

If Portainer can't connect to the endpoint, most likely something went wrong while creating the certificates. Check out the documentation and re-generate the certificates.

# Check if the Docker daemon is running
docker -H my.domain:2376 info

# Check if the DNS settings are correct
dig +short my.domain

Run Portainer on Dokku host

Running Portainer as a container management tool for other Dokku hosts.
Host:

dokku apps:create portainer
dokku domains:add portainer portainer.my.domain

Create persistent storage folder: mkdir -p /var/lib/dokku/data/storage/portainer
Edit app docker-options:

dokku docker-options:add portainer deploy,run "-v /var/lib/dokku/data/storage/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock"

Local

mkdir portainer && cd portainer
git init
git remote add dokku dokku@<host>:portainer
curl https://www.gitignore.io/api/macos%2Cwindows > .gitignore
touch Dockerfile

Edit Dockerfile:

FROM portainer/portainer
EXPOSE 9000

Push to Dokku host:

git add .
git commit -m 'initial'
git push dokku master

Host
Map ports and add SSL using dokku-letsencrypt:

# Map ports
dokku ps:stop portainer
dokku config:set portainer DOKKU_PROXY_PORT_MAP="http:80:9000"

# Add SSL
dokku config:set --no-restart portainer DOKKU_LETSENCRYPT_EMAIL=your@email.tld
dokku letsencrypt portainer

# Check if ports are mapped correctly
dokku config:get portainer DOKKU_PROXY_PORT_MAP

# Should output: "http:80:9000 https:443:9000"

To re-deploy Portainer, first stop the Portainer container (it's using the Docker socket) dokku ps:stop portainer

@practical-engelbart

This comment has been minimized.

Copy link

@practical-engelbart practical-engelbart commented Jun 15, 2020

I use this script to automate it, part of a cloud-init script I made for deploying new VPS.

#!/bin/bash
apt-update
apt-get upgrade -yq > /dev/null 2>&1
apt-get install -yq apt-transport-https ca-certificates curl gnupg-agent software-properties-common > /dev/null 2>&1
apt-get install -yq apache2-utils wget libnss3-tools ccze jq > /dev/null 2>&1

mkdir -p /etc/systemd/system/docker.service.d/
mkdir /root/.docker
mkdir /etc/docker/certs.d/

curl -L https://github.com/cloudflare/cfssl/releases/download/v1.4.1/cfssl_1.4.1_linux_amd64 -o cfssl
chmod +x cfssl
mv cfssl /usr/local/bin/cfssl
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.4.1/cfssljson_1.4.1_linux_amd64 -o cfssljson
chmod +x cfssljson
mv cfssljson /usr/local/bin/cfssljson
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.4.1/cfssl-certinfo_1.4.1_linux_amd64 -o cfssl-certinfo
chmod +x cfssl-certinfo
mv cfssl-certinfo /usr/local/bin/cfssl-certinfo

cat > /root/.docker/ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"server": {
"expiry": "8760h",
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "8760h",
"usages": [
"signing",
"key encipherment",
"client auth"
]
}
}
}
}
EOF

cat > /root/.docker/ca-csr.json <<EOF
{
"CN": "localdomain",
"hosts": [
"localhost",
"localhost.localdomain"
],
"key": {
"algo": "ecdsa",
"size": 521
},
"names": [
{
"C": "US",
"ST": "CA",
"L": "San Francisco",
"OU": "CA",
"O": ""
}
]
}
EOF

cd /root/.docker
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

cat > /root/.docker/server-csr.json <<EOF
{
"CN": "server",
"hosts": [
"localhost.localdomain",
"localhost",
"docker.internal",
"127.0.0.1",
"127.0.1.1",
"172.17.0.1",
"172.20.20.1"
],
"key": {
"algo": "ecdsa",
"size": 521
},
"names": [
{
"C": "US",
"ST": "CA",
"L": "San Francisco",
"OU": "Server",
"O": "<organization"
}
]
}
EOF

cat > /root/.docker/client-csr.json <<EOF
{
"CN": "client",
"hosts": [""],
"key": {
"algo": "ecdsa",
"size": 521
},
"names": [
{
"C": "US",
"ST": "CA",
"L": "San Francisco",
"OU": "Client",
"O": ""
}
]
}
EOF

cfssl gencert
-ca=ca.pem
-ca-key=ca-key.pem
--config=ca-config.json
-profile=server
server-csr.json | cfssljson -bare server

cfssl gencert
-ca=ca.pem
-ca-key=ca-key.pem
-config=ca-config.json
-profile=client
client-csr.json | cfssljson -bare client

cp ca.pem /etc/docker/certs.d/ca.crt
cp client.pem /etc/docker/certs.d/client.pem
cp client-key.pem /etc/docker/certs.d/client.key
chmod -R 600 /etc/docker/certs.d/
chmod 444 /etc/docker/certs.d/ca.crt
chmod 444 /etc/docker/certs.d/client.pem
chmod 400 /etc/docker/certs.d/client.key
chown -R root:docker /etc/docker/certs.d/
chmod 400 ca-key.pem
chmod 444 ca.pem
chmod 400 server-key.pem
chmod 444 server.pem
chmod 400 client-key.pem
chmod 444 client.pem

cp ca.pem /usr/local/share/ca-certificates/localhost.crt
update-ca-certificates

cat > /etc/systemd/system/docker.service.d/override.conf <<EOF
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 --tlsverify --tlscacert /root/.docker/ca.pem --tlscert /root/.docker/server.pem --tlskey /root/.docker/server-key.pem -H unix:///var/run/docker.sock

Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

echo 'export DOCKER_CERT_PATH=/etc/docker/certs.d/' >> /etc/profile
echo 'export DOCKER_HOST=tcp://127.0.0.1:2376'>> /etc/profile
echo 'export DOCKER_TLS_VERIFY=1' >> /etc/profile

systemctl daemon-reload
systemctl restart docker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment