Skip to main content

Docker: HTTPS, ports and volumes

A Docker set-up serves plain HTTP until you put HTTPS in front of it. This page adds that with Caddy, which gets and renews certificates by itself, and explains the ports, the volumes and Docker secrets. It is for whoever manages the server, after one of the two Compose pages.

Why http://<host>:8080 is not enough​

In production the site marks its login cookies Secure, so a browser keeps them only over https://. If you use the site at http://<host>:8080 as a real site, logins do not persist: an admin or member logs in and is sent straight back to the login page. See Put it behind HTTPS. You can use a proxy you already have on the host, or the Caddy service below.

HTTPS with Caddy​

The PostgreSQL stack from Docker Compose: PostgreSQL and MinIO already contains a caddy service in a profile called tls. It does nothing until you ask for the profile. To use the same service with the SQLite file, copy the caddy service and the two Caddy volumes into it.

caddy:
image: caddy:2-alpine
profiles: ["tls"]
restart: unless-stopped
environment:
DOMAIN: ${DOMAIN:?set DOMAIN in .env to use the tls profile}
ports:
- "80:80"
- "443:443"
volumes:
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- app

and under volumes: at the bottom:

caddy-data:
caddy-config:

1. Point the domain at the server first​

Before you start Caddy, make the DNS records for choir.example.org and www.choir.example.org point at this host, and make sure ports 80 and 443 are open. Caddy proves to Let's Encrypt that it controls the name by answering on those ports, so it fails if DNS is not ready.

2. Make the Caddyfile​

The Compose file mounts ./deploy/Caddyfile. It is not in the download, so create the folder and file:

mkdir -p deploy

deploy/Caddyfile:

{$DOMAIN} {
encode zstd gzip
reverse_proxy app:3001
}

# Send www to the bare domain
www.{$DOMAIN} {
redir https://{$DOMAIN}{uri} permanent
}

{$DOMAIN} is filled in from the DOMAIN setting the service passes in. app:3001 is the app container on Compose's own network. This is the Caddyfile that ships with the product's source, and it was checked with caddy validate.

3. Set DOMAIN and start​

In .env:

DOMAIN=choir.example.org
SITE_URL=https://choir.example.org

Then start with the profile:

docker compose --profile tls up -d

(Add -f docker-compose.sqlite.yml if that is your file.) Open https://choir.example.org.

Close the plain port​

The app also publishes its own port, so http://<host>:8080 stays reachable from outside. Once Caddy is in front, make the app listen only on the host itself by changing its ports: line:

ports:
- "127.0.0.1:${HTTP_PORT:-8080}:3001"

Caddy reaches the app over the Compose network, so it does not need the published port at all.

Change the published port​

The host port is the left number in "${HTTP_PORT:-8080}:3001". Set HTTP_PORT in .env (for example HTTP_PORT=9000) and run docker compose up -d again. Never change the 3001 on the right unless you also set PORT for the app to the same number, and update the health check, which looks at port 3001.

Every volume, and what to back up​

VolumeWhereWhat it holdsBack it up?
data/app/data in appIn the SQLite stack: the database, uploaded files, downloaded releases, copies of the database made before an update. In the PostgreSQL stack: downloaded releases only.SQLite stack: yes, it is the whole site. PostgreSQL stack: not needed.
db-data/var/lib/postgresql/data in dbThe PostgreSQL database.Yes, with pg_dump, not by copying the files while it runs.
minio-data/data in minioEvery uploaded file, public and private.Yes.
caddy-data/data in caddyThe HTTPS certificates and Caddy's account with Let's Encrypt.Worth keeping: losing it means certificates are fetched again.
caddy-config/config in caddyCaddy's saved configuration.No.
mysql-data, redis-dataOnly if you switched those blocks onThe MariaDB database; Redis's data (sessions).MariaDB yes; Redis no.

Compose puts the project name in front of each, so data becomes something like choir-docker_data. docker volume ls shows the real names. How to back them up, and restore: What to back up, Back up a single-server site and Back up PostgreSQL, MySQL, S3 storage and Cloudflare.

warning

docker compose down -v deletes volumes, and with them your data. Plain down keeps them.

Secrets in files​

Instead of putting passwords in .env, Docker secrets can mount each one as a file, and a NAME_FILE setting tells the site to read it. This was run for this guide with ADMIN_PASSWORD:

services:
app:
environment:
ADMIN_PASSWORD_FILE: /run/secrets/admin_password
secrets:
- admin_password

secrets:
admin_password:
file: ./secrets/admin_password

Remove ADMIN_PASSWORD from .env: a value that is already set wins over the file. Any setting the server reads works this way, except PORT and DATA_DIR, which must be real environment variables. See Keep secrets in files: NAME_FILE.

Updates in Docker​

With a licence key, the admin panel's Update now downloads the release into the data volume and the launcher runs it, so the update survives recreating the container until you build an image that is as new. If you would rather update by rebuilding the image, set UPDATE_MODE=notify. See Update a Docker site.

Next​

After installing: first login and go-live checklist.