What you will learn

  • How to run RavenDB with HTTPS certificate on Docker
  • How to generate self-signed certificates

Introduction

Running applications in Docker containers offers numerous advantages over installing them directly on a system. Docker ensures isolation and consistency by encapsulating an application and its dependencies, preventing conflicts, and guaranteeing the same behavior across different environments. Its portability allows containers to run on any system that has Linux kernel to be shared, simplifying the movement of applications between environments and eliminating dependency conflicts.

RavenDB provides official Docker images that can be pulled from Docker Hub. These images support both Linux and Windows containers, ensuring broad compatibility across different systems. The images are available in two repositories:

These Docker images support a variety of architectures, including:

  • Linux: arm32, arm64, and amd64
  • Windows: amd64

In this tutorial, we are going to use the multi-platform image ravendb/ravendb:latest.

Prerequisites

Before running RavenDB we need to prepare an environment. First of all we need a place to keep the data for the RavenDB server.

mkdir -p ~/ravendb/data

In order to have a secure connection with the server it’s recommended to use HTTPS protocol. To do that we will need a DNS entry pointing to the server and server certificate. It’s best to use automated tools like certbot for certificate generation and for example AWS Route 53 as DNS service.

For the purpose of this tutorial, to simplify the setup, we will use a self-generated certificate and entry in /etc/hosts. Using self-signed certificates is not recommended for public-facing websites and services. Those are not trusted by default by the OS and must be added as root in the certificate store on every server and client machines that want to communicate. Recommended approach is to use certificates from a certificate authority like Let’s Encrypt, ZeroSSL or any other well-known certificate issuer. There is also an option to use RavenDB initial setup to get a certificate.

mkdir -p ~/ravendb/certs
cd ~/ravendb/certs

Specify URL for the instance (this creates an environment variable that we can use later):

RAVENDB_URL="my-test.instance"

Generate private key and server certificate:

openssl req -x509 -newkey rsa:4096 -sha256 \
   -days 3650   -nodes -keyout my-test.instance.key \
   -out my-test.instance.crt -subj "/CN=$RAVENDB_URL" \
   -extensions v3_req -config <(printf 
"[v3_req]\nkeyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth,clientAuth\nsubjectAltName=DNS:$RAVENDB_URL")

Generate PFX file from private key and server certificate:

openssl pkcs12 -export -out my-test.instance.pfx -inkey my-test.instance.key -in my-test.instance.crt

Remember to save the PFX certificate on your local machine and install it in the root certificate store. You will need it to connect to the RavenDB server for the first time.

When running applications inside Docker containers, like RavenDB, it’s common to manage file permissions on the host system that need to be accessible to the container. In Linux, every user and group is associated with a unique identifier known as a User ID (UID) and a Group ID (GID), respectively. These numerical identifiers are crucial for managing file ownership and permissions. The container’s processes may run under a user with a specific UID and GID, which can differ from those on the host system. In the case of RavenDB, it uses 999 as both the UID and GID for its internal user. This means files on the host system that are mounted into the container must be accessible by the user with these identifiers. Without this adjustment, the container might not have the necessary permissions to read or write to the mounted files, leading to potential errors or malfunctioning of the application.

In summary, while human-readable names are convenient on a single system, UIDs and GIDs ensure consistency across different environments, especially in containerized applications like those running in Docker. Therefore, using numerical identifiers is essential when managing permissions between the host and the Docker container to ensure seamless operation.

Here is the command to change file permissions:

sudo chown -R 999:999 ~/ravendb/

The last step is to obtain a license from https://ravendb.net/buy#developer. There is a free developer license available.

Setup

With the environment prepared we can start building our docker compose. To keep everything in one place let’s create docker-compose.yml file in ~/ravendb directory.

touch ~/ravendb/docker-compose.yml

We can start writing docker compose file with our favorite editor. It starts the same as always with specifying services, naming containers, and selecting Docker image. As mentioned before we will be using RavenDB’s multi-platform image:

version: '3'
  services:
    ravendb:
      container_name: ravendb
      image: ravendb/ravendb:latest

Now we can mount the directories prepared earlier. RavenDB by default keeps the data in /var/lib/ravendb/data. The folder with certificate can be mounted anywhere within the container. We will specify a path to it in the next step. Replace the {username} with your linux username.

volumes:
        - type: bind
          source: /home/{username}/ravendb/data
          target: /var/lib/ravendb/data
        - type: bind
          source: /home/{username}/ravendb/certs
          target: /certs

We would like to be able to access the server. Port binding will help us with that. I’m going to use the default port for the HTTPS protocol. There is also a second port needed for inter-node communication (replication) or data subscription processing. Default used by RavenDB is 38888 so that’s what I’m going to use here as well.

ports:
        - 443:443
        - 38888:38888

Based on values set in previous steps we can configure RavenDB server with environment variables. Several key environment variables ensure that the server runs correctly and securely. Here’s an overview of these options and what they do:


  RAVEN_Setup_Mode=None

Disables the setup wizard. By setting this to None, RavenDB assumes all necessary configurations are provided through environment variables or configuration files, which is ideal for automated deployments.


  RAVEN_License_Eula_Accepted=true

Accepts the End User License Agreement (EULA) for RavenDB. Setting this to true is necessary to run the server without manual intervention, streamlining automated deployment processes.


  RAVEN_ServerUrl=https://0.0.0.0

Specifies what URL RavenDB will bind to for listening on the network. The 0.0.0.0 address is a wildcard that allows the server to accept connections from any network interface.


  RAVEN_PublicServerUrl=https://my-test.instance

Sets the public URL for the RavenDB server. This is the address clients use to connect to the server. Replace https://my-test.instance with the actual domain accessible to clients.


  RAVEN_ServerUrl_Tcp=tcp://0.0.0.0:38888

Configures the server to listen on all network interfaces for TCP connections on port 38888. The 0.0.0.0 address allows connections from any network interface.


  RAVEN_PublicServerUrl_Tcp=tcp://my-test.instance:38888

Sets the public TCP URL for the RavenDB server, used by clients for TCP communication. Replace tcp://my-test.instance:38888 with the actual public address and port number.


  RAVEN_Security.Certificate.Path=/certs/my-test.instance.pfx

Specifies the path to the SSL certificate file used to secure connections to the RavenDB server. The certificate should be in .pfx format and include both the certificate and private key, securing HTTPS and TCP connections.

There are other options to pass certificates to RavenDB, especially useful in containerization scenarios, where the certificate comes from and is renewed by an external facility. You might want to use the approach described here.

Also you might have to recreate the container on certificate renewal, unless you configured the renewal process with RAVEN_Security.Certificate.Renew.Exec environment variable.


  RAVEN_License=’{Put your license here}’

Specifies the RavenDB license string. This is required to activate the license for the RavenDB server, enabling full functionality and compliance with the licensing terms.

These environment variables allow for precise control over the RavenDB server’s network settings, security configurations, and deployment behavior, making it adaptable for various deployment scenarios. For more detailed guidance, refer to the RavenDB documentation: https://ravendb.net/docs/article-page/latest/csharp/server/configuration/configuration-options

environment:
        - RAVEN_Setup_Mode=None
        - RAVEN_License_Eula_Accepted=true
        - RAVEN_ServerUrl=https://0.0.0.0
        - RAVEN_PublicServerUrl=https://my-test.instance
        - RAVEN_ServerUrl_Tcp=tcp://0.0.0.0:388888
        - RAVEN_PublicServerUrl_Tcp=tcp://my-test.instance:38888
        - RAVEN_Security.Certificate.Path=/certs/my-test.instance.pfx

In my case there is one more step required. Because I don’t have any public DNS entry RavenDB server won’t be able to resolve the hostname I used. Therefore, I have to tell docker that my-test.instance is his own address:

extra_hosts:
        - "my-test.instance:127.0.0.1"

Final docker-compose.yml

The final version of docker-compose.yml should look like this:

version: '3'
  services:
    ravendb:
      container_name: ravendb
      image: ravendb/ravendb:latest
      volumes:
        - type: bind
          source: /home/{username}/ravendb/data
          target: /var/lib/ravendb/data
        - type: bind
          source: /home/{username}/ravendb/certs
          target: /certs
      ports:
        - 443:443
        - 38888:38888
      environment:
        - RAVEN_Setup_Mode=None
        - RAVEN_License_Eula_Accepted=true
        - RAVEN_ServerUrl=https://0.0.0.0
        - RAVEN_PublicServerUrl=https://my-test.instance
        - RAVEN_ServerUrl_Tcp=tcp://0.0.0.0:388888
        - RAVEN_PublicServerUrl_Tcp=tcp://my-test.instance:38888
        - RAVEN_Security.Certificate.Path=/certs/my-test.instance.pfx
        - RAVEN_License=’{Put your license here}’
      extra_hosts:
        - "my-test.instance:127.0.0.1"

The only thing left to do is to start the container:

docker compose -f ~/ravendb/docker-compose.yml up -d --force-recreate --wait

Validation

We can now check if the container is running:

docker compose ls

The output should look like this:

$ docker compose ls
  NAME                STATUS              CONFIG FILES
  ravendb             running(1)          ~/ravendb/docker-compose.yml

Let’s try connecting to the RavenDB server using the URL and certificate that were both created in the prerequisites. In my case it’s https://my-test.instance

You can now set up your node.

Summary

In this tutorial we provided a guide on setting up RavenDB with HTTPS in a Docker environment. We outlined the process of creating necessary directories, generating self-signed SSL certificates using OpenSSL, configuring RavenDB via a Docker Compose file, and ensuring proper file permissions. While the tutorial uses self-signed certificates for demonstration purposes, it’s recommended to obtain SSL certificates from trusted Certificate Authorities like Let’s Encrypt or ZeroSSL for production environments to ensure enhanced security and trustworthiness.