Docker’s API is completely unprotected by default except for filesystem permissions on its Unix socket. You should set up TLS when exposing the Docker API over TCP so Docker Engine and your clients can verify each others’ identity. Otherwise anyone with access to the TCP port could browse your Docker containers, start new ones, and run actions as root on your system.

Configured TLS will require clients to present a valid certificate that’s signed by the server’s certificate authority. To get it working, you need to create SSL certificates, then set up Docker Engine to require TLS connections. Docker CLI clients must also be adjusted to expect a TLS server.

Exposing the TCP Socket

You can expose Docker’s TCP socket by using the -H flag to define an extra endpoint when starting the dockerd process. This flag can be repeated multiple times; in this example, both the Unix socket and TCP socket will be available:

/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375

Port 2375 is conventionally used for unencrypted Docker connections. Port 2376 should be used instead once TLS has been set up.

You can configure Docker to use these flags automatically by modifying your Docker service definition. Add an override in /etc/systemd/system/docker.service.d/override.conf that changes the ExecStart line:

[Service]
ExecStart=/usr/bin/dockerd -H ...

Reload systemd to apply the change:

sudo systemctl daemon-reload

Creating Your Certificate Authority

Begin by creating a Certificate Authority (CA) for your TLS configuration. You’ll use this CA to sign your certificates; the server will refuse to communicate with clients that present a certificate from a different CA.

Use OpenSSL to generate private and public CA keys on the machine hosting your Docker server:

# Generate the private key
openssl genrsa -aes256 -out ca-private.pem 4096

# Generate a public key from the private key
openssl req -new -x509 -days 365 -key ca-private.pem -sha256 -out ca-public.pem

image of creating a certificate authority with OpenSSL

You’ll be prompted to supply a passphrase, email address, country code, state and city names, and organizational name to include with your public key. Enter the information in your terminal, pressing enter after each line to progress forwards and create the key.

Generating a Server Key and Certificate Signing Request

Next create a server key and a certificate signing request:

# Generate the server key
openssl genrsa -out server-key.pem 4096

# Generate a certificate signing request
openssl req -subj "/CN=example.com" -sha256 -new -key server-key.pm -out request.csr

image of generating a TLS server key

The certificate signing request (CSR) contains all the information needed to produce a signed certificate. It’s important to check the common name in the CSR is correct for your server. This is specified in the CN field as example.com above; you should set it to the Fully Qualified Domain Name (FQDN) for your server.

 

 

Setting Up Certificate Extensions

Using this CSR would permit connections to the server via its FQDN. You need to specify certificate extensions if you want to add another domain or use an IP address. Create an extensions file with subjectAltName and extendedKeyUsage fields to set this up:

echo subjectAltName = DNS:sub.example.com;IP=192.168.0.1 >> extfile.cnf
echo extendedKeyUsage = serverAuth >> extFile.cnf

This example would additionally permit connections via sub.example.com and 192.168.0.1 .

Generating a Signed Certificate

Now you’re ready to combine all the components and generate a signed certificate:

openssl x509 -req -days 365 -sha256 
    -in request.csr 
    -CA ca-public.pem 
    -CAkey ca-private.pem 
    -CAcreateserial 
    -extfile extfile.cnf 
    -out certificate.pem

This takes the certificate signing request, adds your extension file, and uses your CA’s keys to produce a signed OpenSSL certificate. You’ll need to supply the CA’s passphrase to complete the process.

image of signing a TLS certificate

This certificate is set to expire after a year. You can adjust the -days flag to obtain a useful lifetime for your requirements. You should arrange to generate a replacement certificate before this one expires.

Generating a Client Certificate

Next you should generate another certificate for your Docker clients to use. This must be signed by the same CA as the server certificate. Use an extensions file with extendedKeyUsage = clientAuth to prepare this certificate for use in a client scenario.

# Generate a client key
openssl genrsa -out client-key.pem 4096

# Create a certificate signing request
openssl req -subj '/CN=client' -new -key client-key.pem -out client-request.csr

# Complete the signing
echo extendedKeyUsage = clientAuth >> extfile-client.cnf
openssl x509 -req -days 365 -sha256 
     -in client-request.csr  
     -CA ca-public.pem 
     -CAkey ca-private.pem 
     -CAcreateserial 
     -extfile extfile-client.cnf 
     -out client-certificate.pem

Preparing to Configure Docker

Copy your ca-public.pem, certificate.pem, and server-key.pem files into a new directory ready to reference in your Docker config. Afterwards, copy the ca-public.pem, client-certificate.pem, and client-key.pem files to the machine which you’ll connect from.

You can delete the certificate signing request and extension files in your working directory. Be careful not to lose your private keys as they’re non-recoverable. Without them you’ll be unable to validate certificates or generate renewals.

Configuring the Docker Daemon

Now you can start the Docker daemon with TLS flags referencing your generated certificate and keys. The --tlscacert, --tlscert, and --tlskey parameters specify paths to the respective OpenSSL resources generated above.

/usr/bin/dockerd 
    -H unix:///var/run/docker.sock 
    -H tcp://0.0.0.0:2376 
    --tlsverify 
    --tlscacert=ca-public.pem 
    --tlscert=certificate.pem 
    --tlskey=server-key.pem

Adding the --tlsverify flag enables enforcement of TLS connections. Clients without a matching certificate will be blocked from accessing Docker’s TCP socket.

Configuring the Docker Client

Activate TLS on the client by supplying TLS flags when you use the docker command. You must also add the -H flag to specify the remote Docker socket address to connect to. From the client’s perspective, --tlsverify means the command will only connect to servers with a TLS certificate signed by the same certificate authority as its own.

docker 
    -H tcp://0.0.0.0:2376 
    --tlsverify 
    --tlscacert=ca-public.pem 
    --tlscert=client-certificate.pem 
    --tlskey=client-key.pem 
    ps

Supplying these flags each time you use the CLI gets repetitive very quickly. If you’ll mostly be working with the same TLS-protected host, set the DOCKER_HOST and DOCKER_TLS_VERIFY environment variables in your shell profile. Copy your certificates files to ca, cert, and key inside your ~/.docker directory. These correspond to Docker’s --tls flags and define a default certificate for the client.

export DOCKER_HOST=tcp://0.0.0.0:2376
export DOCKER_TLS_VERIFY=1

You can simplify working with multiple hosts using a mixture of local, remote, unsecured, and TLS connections by setting up Docker contexts. This feature lets you switch between targets using Docker CLI commands.

The Docker client also supports alternative verification modes. Using a mixture of tls, tlscacert, tlscert, tlskey, and tlsverify flags activates varying TLS enforcement levels.

With just tls set, Docker will authenticate the server using the default CA pool. Adding the tlscacert and tlsverify flags without a client key will enforce the server uses the given CA without any other checks. Omitting tlscacert and tlsverify but including the other three keys will verify the client’s certificate without authenticating the server’s CA.

Conclusion

Protecting Docker’s TCP socket with TLS certificates lets you expose the API more safely by preventing connections from unauthorized clients. Actors who port scan your network will be barred from connecting to Docker, giving you a layer of protection that stops your machine being compromised with root-level privileges.

Once you’ve generated your certificates, you can use them to authenticate with the Docker CLI or your own HTTP clients. Curl will accept them as --cert, --key, and --cacert flags, for example.

TLS is only one component of a secured Docker API instance. It provides encryption and an assurance that clients are trusted but is not a granular access control mechanism.

If you want to limit what individual clients are allowed to do, you should set up a Docker Engine authorization plugin. Plugins can contact an external service to determine whether a particular API request is allowed to proceed. As an alternative, you could use a reverse proxy in front of your TCP socket to enforce access control before requests reach Docker.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »