TLS secured TCP exposed Docker daemon on Dokku host - setup
- Create certificates
- Edit Docker options
- Restart Docker
- Copy client certificates from host
- (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
Portainer on Dokku host
RunRunning 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
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