Deploy a docker registry, with portus, and clair
Setting up a secure doker registry, behind apache with portus and clair.
I recently wrote an article on how to deploy a secure registry on a kubernetes cluster, at this time I knew PortUs but I thought it was not enough ready for production.
Last week we need to setup a docker registry for a client, so we thoughts we need to give another chance to PortUs, and it was a succes.
There is also a big difference with the previous needs, now we are on a single server, and we will deploy the registry using docker compose.
I have found a lot of help in the examples from the PortUs documentation : https://github.com/SUSE/Portus/tree/master/examples/compose
As we need to use Apache Httpd Server with that client, and that the httpd server is running outside docker, si I needed to adapt the examples.
I also use an SSL certificate from Let’s Encrypt, and a public URL.
I made this setup on a fresh Debian install.
So I assume that you have installed Docker and Docker Compose.
Prepare Docker deployment
I started from the portus docker compose example in the PortUs, and I made some little changes.
So first create a working directory :
mkdir -p ./my-registry/registry ./my-registry/clair ./my-registry/secrets
mkdir -p /var/lib/portus/registry /var/lib/portus/mariadb
Then create a .env
file for Docker Compose :
MACHINE_FQDN=registry.me.local
DATABASE_PASSWORD=MXmE4mkesNAtYS6P
PORTUS_PASSWORD=vqgBP7Zuf7r86k3F
SECRET_KEY_BASE=3SL3ZbfLTHBqnJ2Eamd7ygE
- MACHINE_FQDN is the Full Qualified Domain Name of your registry, mean you will do
docker login registry.me.local
on the clients. - DATABASE_PASSWORD the password for the Portus MySQL Database
- PORTUS_PASSWORD the password of the special portus user
- SECRET_KEY_BASE the secret key for Portus, to create Session JSON Web TOkens
Then create certificates for communications between your docker registry and your portus server.
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout portus.key -out portus.crt
Then from the portus docker compose example I copied the following files :
wget https://raw.githubusercontent.com/SUSE/Portus/master/examples/compose/clair/clair.yml -o clair/clair.yml
wget https://raw.githubusercontent.com/SUSE/Portus/master/examples/compose/clair/clair.yml -o clair/clair.yml
wget https://raw.githubusercontent.com/SUSE/Portus/master/examples/compose/registry/init -o registry/init
Now here is the adapted docker-compose.yaml
file
version: "2"
services:
portus:
image: opensuse/portus:2.3.5
environment:
- PORTUS_MACHINE_FQDN_VALUE=${MACHINE_FQDN}
- PORTUS_SECURITY_CLAIR_SERVER=http://clair:6060
# DB. The password for the database should definitely not be here. You are
# probably better off with Docker Swarm secrets.
- PORTUS_DB_HOST=db
- PORTUS_DB_DATABASE=portus_production
- PORTUS_DB_PASSWORD=${DATABASE_PASSWORD}
- PORTUS_DB_POOL=5
# Secrets. It can possibly be handled better with Swarm's secrets.
- PORTUS_SECRET_KEY_BASE=${SECRET_KEY_BASE}
- PORTUS_KEY_PATH=/certificates/portus.key
- PORTUS_PASSWORD=${PORTUS_PASSWORD}
# SSL
- PORTUS_CHECK_SSL_USAGE_ENABLED='false'
# Since we have no nginx in insecure mode, portus have to
# serve the static files
- RAILS_SERVE_STATIC_FILES='true'
ports:
- 3000:3000
depends_on:
- db
links:
- db
volumes:
- ./secrets:/certificates:ro
background:
image: opensuse/portus:2.3.5
depends_on:
- portus
- db
environment:
# Theoretically not needed, but cconfig's been buggy on this...
- CCONFIG_PREFIX=PORTUS
- PORTUS_MACHINE_FQDN_VALUE=${MACHINE_FQDN}
- PORTUS_SECURITY_CLAIR_SERVER=http://clair:6060
# DB. The password for the database should definitely not be here. You are
# probably better off with Docker Swarm secrets.
- PORTUS_DB_HOST=db
- PORTUS_DB_DATABASE=portus_production
- PORTUS_DB_PASSWORD=${DATABASE_PASSWORD}
- PORTUS_DB_POOL=5
# Secrets. It can possibly be handled better with Swarm's secrets.
- PORTUS_SECRET_KEY_BASE=${SECRET_KEY_BASE}
- PORTUS_KEY_PATH=/certificates/portus.key
- PORTUS_PASSWORD=${PORTUS_PASSWORD}
- PORTUS_BACKGROUND=true
links:
- db
volumes:
- ./secrets:/certificates:ro
db:
image: library/mariadb:10.0.23
command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --init-connect='SET NAMES UTF8;' --innodb-flush-log-at-trx-commit=0
environment:
- MYSQL_DATABASE=portus_production
# Again, the password shouldn't be handled like this.
- MYSQL_ROOT_PASSWORD=${DATABASE_PASSWORD}
volumes:
- /var/lib/portus/mariadb:/var/lib/mysql
registry:
image: library/registry:2.6
environment:
# Authentication
REGISTRY_AUTH_TOKEN_REALM: https://${MACHINE_FQDN}/v2/token
REGISTRY_AUTH_TOKEN_SERVICE: ${MACHINE_FQDN}
REGISTRY_AUTH_TOKEN_ISSUER: ${MACHINE_FQDN}
REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE: /secrets/portus.crt
# Portus endpoint
REGISTRY_NOTIFICATIONS_ENDPOINTS: >
- name: portus
url: https://${MACHINE_FQDN}/v2/webhooks/events
timeout: 2000ms
threshold: 5
backoff: 1s
volumes:
- /var/lib/portus/registry:/var/lib/registry
- ./secrets:/secrets:ro
- ./registry/config.yml:/etc/docker/registry/config.yml:ro
- ./registry/init:/etc/docker/registry/init:ro
ports:
- 5000:5000
- 5001:5001 # required to access debug service
links:
- portus:portus
clair:
image: quay.io/coreos/clair:v2.0.6
restart: unless-stopped
depends_on:
- postgres
links:
- postgres
ports:
- "6060-6061:6060-6061"
volumes:
- /tmp:/tmp
- ./clair/clair.yml:/clair.yml
command: [-config, /clair.yml]
postgres:
image: library/postgres:10-alpine
environment:
POSTGRES_PASSWORD: portus
So now you should be able to start your containers :
docker-compose up
Apache VirtualHost
So now we need to configure apache and create SSL certificates.
For creating SSL certificates and HTTPS VirtualHost I’m used to create the HTTP VirtualHost and create certificates with Certbot so it will also create the HTTPS VirtualHost.
Here is the http vhost :
<VirtualHost *:80>
ServerName registry.me.local
# This is for letsencrypt
DocumentRoot /var/www/html
ProxyPassMatch ^/.well-known !
ProxyPreserveHost on
ProxyRequests off
# Default IHM is portus
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
# Token for Auth
ProxyPass /v2/token http://localhost:3000/v2/token
ProxyPassReverse /v2/token http://localhost:3000/v2/token
ProxyPass /v2/webhooks/events http://localhost:3000/v2/webhooks/events
ProxyPassReverse /v2/webhooks/events http://localhost:3000/v2/webhooks/events
# The main registry
ProxyPass /v2 http://localhost:5000/v2
ProxyPassReverse /v2 http://localhost:5000/v2
RewriteEngine on
RewriteCond %{SERVER_NAME} =registry.btb.ovh
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
</VirtualHost>
Then create certificates :
certbot --authenticator webroot --installer apache
And answer all questions :
Which names would you like to activate HTTPS for?
-------------------------------------------------------------------------------
1: registry.me.local
-------------------------------------------------------------------------------
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel):1
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for registry.me.local
Select the webroot for registry.me.local:
-------------------------------------------------------------------------------
1: Enter a new webroot
-------------------------------------------------------------------------------
Press 1 [enter] to confirm the selection (press 'c' to cancel): 1
Input the webroot for registry.me.local: (Enter 'c' to cancel):/var/www/html
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0002_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0002_csr-certbot.pem
Created an SSL vhost at /etc/apache2/sites-available/01.registry.me.local-le-ssl.conf
Deploying Certificate to VirtualHost /etc/apache2/sites-available/01.registry.me.local-le-ssl.conf
Enabling available site: /etc/apache2/sites-available/01.registry.me.local-le-ssl.conf
Please choose whether HTTPS access is required or optional.
-------------------------------------------------------------------------------
1: Easy - Allow both HTTP and HTTPS access to these sites
2: Secure - Make all requests redirect to secure HTTPS access
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting vhost in /etc/apache2/sites-available/01.registry.me.local.conf to ssl vhost in /etc/apache2/sites-available/01.registry.me.local-le-ssl.conf
-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://registry.me.local
You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=test1.sso.btb.ovh
-------------------------------------------------------------------------------
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/registry.me.local/fullchain.pem. Your cert
will expire on 2018-12-19. To obtain a new or tweaked version of
this certificate in the future, simply run certbot again with the
"certonly" option. To non-interactively renew *all* of your
certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Then you will have to edit the HTTPS VirtualHost :
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName registry.me.local
# This is for enebling letsencrypt
DocumentRoot /var/www/html
ProxyPassMatch ^/.well-known !
# This is needed by the docker registry
Header set Host "registry.me.local"
RequestHeader set X-Forwarded-Proto "https"
ProxyPreserveHost on
ProxyRequests off
ProxyPass /v2/token http://localhost:3000/v2/token
ProxyPassReverse /v2/token http://localhost:3000/v2/token
ProxyPass /v2/webhooks/events http://localhost:3000/v2/webhooks/events
ProxyPassReverse /v2/webhooks/events http://localhost:3000/v2/webhooks/events
ProxyPass /v2 http://localhost:5000/v2
ProxyPassReverse /v2 http://localhost:5000/v2
# Defualt ihm is portus
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
SSLCertificateFile /etc/letsencrypt/live/registry.me.local/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/registry.me.local/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
Remember to restart Apache :
systemctl restart apache2
Then everything should be up and running
Test it
You need to go to https://registry.me.local, then see the PortUs screen to create the admin user.
Create it, then configure the registry :
- Name of your choice
- Registry : registry.me.local
- Use SSL : Checked
Then on a docker client :
docker login registry.me.local
Login with the admin.
Then try push :
docker tag busybox:latest registry.me.local/busybox:latest
docker push registry.me.local/busybox:latest
The push refers to repository [registry.me.local/busybox]
f9d9e4e6e2f0: Pushed
latest: digest: sha256:5e8e0509e829bb8f990249135a36e81a3ecbe94294e7a185cc14616e5fad96bd size: 527
So it works ! Enjoy It.