Quickstart with Docker Compose
This guide provides instructions for running your own certificate authority using docker compose. This is the quickest and easiest way to run django-ca, especially if you do not care to much about custom configuration or extending django-ca.
This tutorial will give you a CA with
A root and intermediate CA.
ACMEv2 support (= get certificates using certbot).
A browsable admin interface and a REST API.
Certificate revocation using CRLs and OCSP.
ACMEv2, admin interface and REST API are served with HTTPS using Let’s Encrypt certificates.
Note
This tutorial uses structured-tutorials.
This means that the documentation you see here is rendered from a configuration file and can also be run locally to verify correctness and completeness.
Requirements
This guide assumes that you have moderate knowledge of running servers, installing software and how TLS certificates work.
The guide assumes you use a dedicated server to set up your certificate authority and does not account for potential conflicts with other software like ports, directories or container names.
A certificate authority needs plain HTTP for CRL and OCSP access and HTTPS for ACMEv2. Using standard ports are strongly recommended (clients might fail otherwise). If you do not need any of the mentioned features, you can use all other features from the command line and do not need a web server.
Setup DNS
Before you set up your certificate authority, you need a domain name that points to the server where you install django-ca. Since the domain is encoded in CA certificates, it cannot be easily changed later.
This guide assumes that ca.example.com is the name that points to the server where you are setting up your
certificate authority.
Required software
To run django-ca, you need Docker (at least version 19.03.0) and Docker Compose (at least version 1.28.0). You also need certbot to acquire Let’s Encrypt certificates for the admin interface. OpenSSL is used to generate the DH parameter file. On Debian/Ubuntu, simply do:
user@host:~$ sudo apt update
user@host:~$ sudo apt install docker.io docker-compose certbot openssl
For a different OS, please read Install Docker, Install docker compose and Get certbot.
If you want to run docker as a regular user, you need to add your user to the docker
group and log in again:
user@host:~$ sudo adduser `id -un` docker
user@host:~$ sudo su `id -un`
Get configuration
Note
Because of how Docker Compose works, it is better to put the file in a sub-directory and not directly
into your home directory. We assume you put all files into ~/ca/ from now on:
user@host:~$ mkdir ~/ca/
user@host:~$ cd ~/ca/
To run django-ca, you’ll need a couple of files:
dhparam.pem, the DH parameters (required for TLS connections).
conf/, a directory with YAML configuration files.
compose.yaml, the configuration for Docker Compose.
compose.override.yaml, system-local configuration overrides for Docker Compose.
.env, the environment file for Docker Compose.
nginx-reload.sh, a certbot deploy hook to reload NGINX after certificate renewal.
Read the sections below how to retrieve or generate all these files.
Generate DH parameters
The TLS configuration requires that you generate a DH parameter file, which used by some TLS ciphers. You can generate it with:
user@host:~/ca/$ openssl dhparam -dsaparam -out dhparam.pem 4096
Generating DSA parameters, 4096 bit long prime
...
Add configuration files
Changed in version 2.5.0: Previous versions documented a single file in ~/ca/, the new style allows you to split the
configuration into multiple files.
django-ca is configured via a YAML configuration files. It is not strictly required, as the defaults are fine in most cases. Creating at least an empty file is recommended, as it will make any future changes easier.
Note
Do not set CA_DEFAULT_HOSTNAME and CA_URL_PATH here! They are set in .env, as the NGINX container also uses them.
First, simply create a configuration directory:
user@host:~/ca/$ mkdir conf
You can create any number of files in that directory, and they will be read in alphabetical order. Here we add an example that just enables the REST API:
# Enable the REST API
CA_ENABLE_REST_API: true
You can configure any Django setting or any of the custom settings here. Almost all settings can be changed later, if that is not the case, the settings documentation mentions it.
Add compose.yaml
Docker Compose needs a configuration file, compose.yaml. You
can also download the file for other versions from github.
You can also get versions for specific versions of django-ca from the table below, which also shows bundled third-party Docker images.
Warning
When updating, check if the PostgreSQL version has been updated. If yes, see PostgreSQL update for upgrade instructions.
Version |
Redis |
PostgreSQL |
NGINX |
|---|---|---|---|
8 |
16 |
1.28 |
|
8 |
16 |
1.28 |
|
7 |
16 |
1.26 |
|
7 |
16 |
1.26 |
|
7 |
16 |
1.26 |
|
7 |
16 |
1.26 |
|
7 |
16 |
1.26 |
|
7 |
16 |
1.26 |
|
7 |
16 |
1.24 |
|
7 |
16 |
1.24 |
|
7 |
12 |
1.24 |
Note that until django-ca==2.1.1, this file was called docker-compose.yml.
Add compose.override.yaml
The default compose.yaml does not offer HTTPS, because too many details (cert location, etc.)
are different from system to system. We need to add a compose override file to open the port and map the
directories with the certificates into the container. Simply add a file called compose.override.yaml
next to your main configuration file:
services:
beat:
volumes: &volumes
# If you upgrade from 2.4.0 or earlier, either move localsettings.yaml
# to conf/, or add this line:
#- ./localsettings.yaml:/usr/src/django-ca/ca/conf/compose/99-localsettings.yaml
- ./conf:/usr/src/django-ca/ca/conf/local/:ro
backend:
volumes: *volumes
frontend:
volumes: *volumes
webserver:
volumes:
- "/etc/letsencrypt/live/${DJANGO_CA_CA_DEFAULT_HOSTNAME}:/etc/certs/live/${DJANGO_CA_CA_DEFAULT_HOSTNAME}/:ro"
- "/etc/letsencrypt/archive/${DJANGO_CA_CA_DEFAULT_HOSTNAME}:/etc/certs/archive/${DJANGO_CA_CA_DEFAULT_HOSTNAME}/:ro"
- ./dhparam.pem:/etc/nginx/dhparams/dhparam.pem:ro
- ./acme/:/usr/share/django-ca/acme/:ro
ports:
- 443:443
This will work if you get your certificates using certbot or a similar client. If your private key in
public key chain is named different, you can set NGINX_PRIVATE_KEY and NGINX_PUBLIC_KEY in your
.env file below.
Add .env file
Some settings in django-ca can be configured with environment variables (except where a more complex
structure is required). Simply create a file called .env next to compose.yaml.
For a quick start, there are only a few variables you need to specify:
# Optionally use a different Docker tag for django-ca. For available tags, see:
# https://hub.docker.com/r/mathiasertl/django-ca
#DJANGO_CA_VERSION=alpine
# The hostname for your CA.
# WARNING: Changing this requires new CAs (because the hostname goes into the certificates).
DJANGO_CA_CA_DEFAULT_HOSTNAME=ca.example.com
# The URL base path used for ACMEv2/OCSP/CRL URLs. If given, the path **must** end with a slash.
#
# If you're upgrading from a previous version and have existing CAs, uncomment or set to "django_ca/".
#
# WARNING: Changing this requires new CAs (because the path goes into the certificates).
DJANGO_CA_CA_URL_PATH=
# PostgreSQL superuser password (required by the Docker image), see also:
# https://hub.docker.com/_/postgres
#
# Generate a secure password e.g. with "openssl rand -base64 32"
POSTGRES_PASSWORD=mysecretpassword
# NGINX TLS configuration
NGINX_TEMPLATE=tls
NGINX_PRIVATE_KEY=/etc/certs/live/ca.example.com/privkey.pem
NGINX_PUBLIC_KEY=/etc/certs/live/ca.example.com/fullchain.pem
Add nginx-reload.sh file
nginx-reload.sh is a certbot deployment hook that will reload the web server when the certbot
certificate is renewed:
#!/usr/bin/env sh
set -ex
set -uo pipefail
# SETTING: Location of your Docker Compose setup.
# The default is to use the location of this script. Change this if you moved
# it to a different location (e.g. /usr/local/bin/)
SCRIPT_PATH=$(cd "$(dirname "$0")" >/dev/null 2>&1 && pwd)/$(basename "$0")
SERVICE=webserver
COMPOSE_DIR="$SCRIPT_PATH"
COMPOSE_FILE="$SCRIPT_PATH/compose.yaml"
docker compose --project-directory "$COMPOSE_DIR" -f "$COMPOSE_FILE" exec "$SERVICE" nginx -t
docker compose --project-directory "$COMPOSE_DIR" -f "$COMPOSE_FILE" exec "$SERVICE" nginx -s reload
Certbot requires the script to be executable:
user@host:~/ca/$ chmod a+rx nginx-reload.sh
Recap
By now, you should have five files and one directory in ~/ca/:
user@host:~/ca/$ ls -A
compose.override.yaml compose.yaml conf dhparam.pem nginx-reload.sh .env
Get initial certificates
Some endpoints of the CA (ACMEv2, REST API and the admin interface) are available via HTTPS. In our tutorial, we will use Let’s Encrypt certificates for maximum compatibility for clients.
You could also use certificates from a CA managed by django-ca itself, but such a setup could lead to a situation where endpoints are no longer working due to faulty (e.g. expired) certificates, but you need working endpoints to renew them.
Retrieving initial certificates must be done before you run docker compose, as both bind to port 80 (HTTP):
user@host:~/ca/$ sudo certbot certonly --standalone -d ca.example.com \
> --deploy-hook $(pwd)/nginx-reload.sh
The above example uses the standalone plugin to fulfill a HTTP-01 challenge type. This challenge requires your server to export port 80 to the internet. If
that does not work for you but you still want to use Let’s Encrypt, you can use any of the many plugins.
Start your CA
Now, you can start django-ca for the first time. Inside the folder with all your configuration, run docker compose (and verify that everything is running):
user@host:~/ca/$ docker compose up -d
...
user@host:~/ca/$ docker compose ps
Name Command State Ports
-----------------------------------------------------------------------------------
ca_backend_1 ./celery.sh -l info Up
ca_cache_1 docker-entrypoint.sh redis ... Up
...
By now, you should be able to see the admin interface (but not log in yet - you haven’t created a user yet). Simply go to https://ca.example.com/admin/.
Verify setup
You can run the deployment checks for your setup, which should not return any issues:
user@host:~/ca/$ docker compose exec beat manage check --deploy
System check identified no issues (4 silenced).
user@host:~/ca/$ docker compose exec backend manage check --deploy
System check identified no issues (4 silenced).
user@host:~/ca/$ docker compose exec frontend manage check --deploy
System check identified no issues (4 silenced).
Create admin user and set up CAs
Inside the backend container, manage is an alias for manage.py.
Custom management commands are documented in Command-line interface. You need to create a user (that can log into the admin interface) and create a root and intermediate CA:
user@host:~/ca/$ docker compose exec backend manage createsuperuser
...
user@host:~/ca/$ docker compose exec backend manage init_ca --path-length=1 Root CN=Root
<root-serial>
user@host:~/ca/$ docker compose exec backend manage init_ca \
> --path=ca/shared/ --parent=Root --acme-enable \
> Intermediate CN=Intermediate
<intermediate-serial>
There are a few things to break down in the above commands:
The subject (
CN=...) in the CA is only used by browsers to display the name of a CA. It can be any human readable value and does not have to be a domain name.The first positional argument to
init_ca, (“Root”, “Intermediate”) is just a human readable name used to identify the CA within the command-line interface and web interface. Unlike the CommonName, it must be unique.The
--path=ca/shared/parameter for the intermediate CA means that you can use the admin interface to issue certificates. Without it, the web server has no access to the private key for your CA.The
--pathlen=1parameter for the root CA means that there is at most one level of intermediate CAs.
Use your CAs
After adding your Root CA to your system, you can use the admin interface at
https://ca.example.com/admin/ with the credentials you created above to create new certificates or revoke
certificates. You cannot issue certificates with the “Root” CA, since you did not pass
--path=ca/shared/ when creating it.
CRL and OCSP services are provided by default, so there is nothing you need to do to enable them.
You can use the Command-line interface for creating new CAs as well as issuing, renewing and revoking
certificates.
The manage.py script can be invoked via docker compose exec backend manage, for example:
user@host:~/ca/$ docker compose exec backend manage list_cas
<serial> - Intermediate
<serial> - Root
To sign a certificate from the command line, simply invoke the sign_cert command.
user@host:~/ca/$ docker compose exec backend manage sign_cert --ca=Intermediate \
> --subject="CN=example.com"
Please paste the CSR:
-----BEGIN CERTIFICATE REQUEST-----
...
To pass data from stdin, for example to sign a certificate non-interactively by passing the CSR from stdin,
you need to pass the -T parameter to docker-compose exec:
user@host:~/ca/$ openssl genrsa -out stdin.example.com.key 4096
user@host:~/ca/$ openssl req -new -key stdin.example.com.key -out stdin.example.com.csr \
> -utf8 -batch -subj /CN=stdin.example.com
user@host:~/ca/$ cat stdin.example.com.csr | docker compose exec -T backend manage sign_cert \
> --ca=Intermediate --subject="CN=stdin.example.com"
Add CA to your system
To get the certificate for your Root CA, you can use the admin interface or the view_ca command:
user@host:~/ca/$ docker compose exec backend manage view_ca --output-format pem Root > \
> root.pem
You can add this file directly to the list of known CAs in your browser.
Distributions usually provide instructions for how to add a CA to the whole system, see for example these instructions for Debian/Ubuntu.
Use ACME with certbot
If you enabled ACMEv2 support, all you need to do is enable ACMEv2 for the intermediate CA
using the admin interface (or using docker compose exec backend manage edit_ca). After that, you can
retrieve a certificate using a simple certbot command:
$ sudo certbot register --server https://ca.example.com/acme/directory/
$ sudo certbot certonly --server https://ca.example.com/acme/directory/ ...
Automatic certificate renewal
If you used the certbot certonly --standalone to retrieve Let’s Encrypt certificates (see above), you still need to configure automatic certificate renewal.
The current setup will not work, as port 80 is now occupied by nginx. Tell certbot to renew certificates
using the “webroot” plugin instead:
user@host:~/ca/$ sudo certbot certonly --force-renew --webroot -w $(pwd)/acme \
> -d ca.example.com --deploy-hook $(pwd)/nginx-reload.sh
If you used any other ACMEv2 authentication method (e.g. a DNS-based setup), you probably don’t need to do anything here.
Backup
To backup your data, you need to store the PostgreSQL database and the private key files for your certificate authorities.
If possible for you, you can first stop the frontend and backend containers to make absolutely sure
that you have a consistent backup:
user@host:~/ca/$ docker compose stop frontend
user@host:~/ca/$ docker compose stop backend
Create a database backup:
user@host:~/ca/$ docker compose exec db pg_dump -U postgres postgres > db.backup.sql
Backing up Docker volumes is not as straight forward as maybe it should be, please see the official documentation for more information.
You should always backup /var/lib/django-ca/certs/ from both the backend and the frontend
container.
Here is an example that should work for the backend container.:
user@host:~/ca/$ docker run -it --rm --volumes-from `basename $PWD`_backend_1 \
> -v $(pwd):/backup ubuntu tar czf /backup/backend.tar.gz /var/lib/django-ca/certs/
user@host:~/ca/$ tar tf backend.tar.gz
var/lib/django-ca/certs/
var/lib/django-ca/certs/ca/
var/lib/django-ca/certs/ca/1BBB69C1D3B64AB5EF39C2946015F57A0FB04107.key
var/lib/django-ca/certs/ca/shared/
var/lib/django-ca/certs/ca/shared/secret_key
...
Update
When updating, first check the ChangeLog for any breaking changes. Under Update you’ll also find notes on any manual steps you might need to take.
Warning
Updating from django-ca 1.28.0 or earlier? Please see PostgreSQL update.
Updating from django-ca 1.18.0 or earlier? Please see Update from 1.18 or earlier.
Remember to backup your data before you perform any update.
In general, updating django-ca is done by getting the latest version of compose.yaml and then simply recreating the containers:
user@host:~/ca/$ curl -O https://.../compose.yaml
user@host:~/ca/$ docker compose up -d
PostgreSQL update
When a new version compose.yaml includes a new version of PostgreSQL, you have to take some
extra steps to migrate the PostgreSQL database.
Before you upgrade, back up your PostgreSQL database as usual:
user@host:~/ca/$ docker compose down
user@host:~/ca/$ docker compose up -d db
user@host:~/ca/$ docker compose exec db pg_dump -U postgres -d postgres > backup.sql
Now update compose.yaml but then only start the database:
user@host:~/ca/$ curl -O https://.../compose.yaml
user@host:~/ca/$ docker compose up -d db
Once the database is started, update the database with data from your backup and normally start your setup:
user@host:~/ca/$ cat backup.sql | docker compose exec -T db psql -U postgres -d postgres
user@host:~/ca/$ docker compose up -d
Forgot to backup?
If you forgot to backup your database and started the update already, don’t panic. Whenever we update the
PostgreSQL version, we use a a new Docker volume. You should be able to reset compose.yaml and
then proceed to do the backup normally.