OpenSSL is a cryptographic library that developers embed into applications to implement secure protocols—it provides the encryption engine for HTTPS, email security, VPNs, and certificate management, but it does not provide connectivity itself.
On the other hand, OpenSSH is a complete suite of tools for secure remote access and file transfer—it handles logins, file copying, tunneling, and port forwarding between machines, using its own independent cryptographic implementation (though it can also be configured to work with OpenSSL).
In essence: OpenSSL is the cryptographic toolkit that secures data in transit across various protocols, while OpenSSH is the connectivity toolset that secures remote machine access and operations—they serve different purposes, operate independently, and are not dependent on one another despite occasional integration.
Analogy: OpenSSL answers, "How do I encrypt this data?" while OpenSSH answers "How do I securely reach that machine?"
Create the following folders which will be mapped to the container volumes:
container-volumes\ubuntu\home-studentcontainer-volumes\nginx\certs
Create the .env file based on the example provided in env.example.
Execute the following command in the terminal to build the Linux (Ubuntu) Server image and then use the image to create the container:
docker compose \
-f docker-compose-dev.yaml \
up -d ubuntu-serverAnd then execute the following to access the bash terminal inside the container:
docker exec -it --user student customized-ubuntu-server-smm bashMost Linux distributions ship with OpenSSL by default because system utilities depend on it. However, assuming that there is no OpenSSL installed, below are the installation steps in a Linux Server (Ubuntu server).
Execute:
sudo apt update
sudo apt install opensslVerify installation:
openssl version
which opensslExecute:
vim /home/student/openssl.cnfPress i for Insert Mode in vim and paste the following inside the openssl.cnf file:
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
x509_extensions = v3_req
distinguished_name = dn
[ dn ]
C = KE
ST = Nairobi County
L = Nairobi
O = Class Lab
OU = IT Department
CN = localhost
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
DNS.2 = customized-ubuntu-server-smm
IP.1 = 127.0.0.1
Press Esc and then type :wq to write the changes to the file and quit Vim.
Execute:
mkdir /home/student/certsRun the following command in the bash terminal to generate the public certificate using OpenSSL:
openssl req -x509 -nodes -days 90 \
-newkey rsa:2048 \
-keyout /home/student/certs/selfsigned.key \
-out /home/student/certs/selfsigned.crt \
-config /home/student/openssl.cnfConfirm that the certificate has been created:
ls -al /home/student/certs/The command does the following 2 tasks:
-
Creates a new private key → /home/student/certs/selfsigned.key This is secret. YOU SHOULD NOT SHARE IT PUBLICLY. Nginx uses it to prove that it is the server.
-
Creates a new public certificate → /home/student/certs/selfsigned.crt This is the self-signed certificate. It contains the “public half” of your identity. Browsers use it to set up encrypted communication.
Key Points to Note:
-
With a Self-Signed Certificate (our current setup for educational purposes): We generate and issue both the private key (.key) and the certificate (.crt) ourselves. The browser says: “I do not know this Certificate Authority that issued this certificate (you issued the certificate yourself), so I cannot trust this identity.” Encryption works (data is scrambled), but identity is not trusted. Anyone could generate a certificate for localhost or even google.com if it is self-signed.
-
With a Trusted Certificate Authority (real-world setup) You create a Certificate Signing Request (CSR) This file contains your domain name (e.g.,
yourdomain.co.ke) and your public key. You generate the public key from your private key. You then send the CSR to a Certificate Authority (CA)
Examples of CAs: Let’s Encrypt (free), DigiCert, GlobalSign, etc.
The CA confirms that you actually own yourdomain.co.ke.
The CA then signs your CSR. This produces a certificate (yourdomain.crt) that says:
“The CA vouches that the owner of this public key really owns yourdomain.co.ke.”
The difference is that browsers trust your public certificate because they already trust the CA.
You can then deploy the certificate + private key in Nginx.
This means, copy the following created files:
container-volumes\ubuntu\home-student\certs\selfsigned.crtandcontainer-volumes\ubuntu\home-student\certs\selfsigned.key
to the mapped container volume container-volumes\nginx\certs.
container-volumes/nginx/nginx.conf will then use the certificate + private key based on its configuration:
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/certs/selfsigned.crt;
ssl_certificate_key /etc/nginx/certs/selfsigned.key;
The deployment is completed when the Nginx image is built and the updated configuration (container-volumes/nginx/nginx.conf) file is uploaded to the running Nginx container.
Install and use SSH to understand the different use cases of SSL and SSH.
Execute the following in the Linux (Ubuntu) server container:
sudo apt update
sudo apt install -y openssh-serverVerify installation:
ssh -V
# Commented out because Docker containers do not contain a full Linux OS that has the standard `systemctl` installed
# systemctl status ssh
# Instead of `systemctl`, we use the following in a container:
sudo service ssh startYou can now use SSH (not SSL) to access the container running Linux. Execute the following in another terminal:
ssh student@localhost -p 2222The building of the images in the subsequent steps is done using the following Docker Compose files: docker-compose.yaml followed by docker-compose-dev.yaml. Execute the following to build the images and run the Docker containers (this enables you to perform Step 3 and Step 4):
docker compose \
-f docker-compose.yaml \
-f docker-compose-dev.yaml \
up -d \
--scale nginx=1 \
--scale flask-gunicorn-app=2Docker Compose in turn accesses the following two Dockerfiles to build the required images:
The application server is made up of Gunicorn, a Web-Server Gateway Interface (WSGI) application server →
Gunicorn runs in a Python environment to access Flask → Flask serves the model trained using Python through an API.

The reverse proxy is made up of the NGINX web server.

A reverse proxy:
- Terminates SSL (handles HTTPS).
- Routes requests to the right backend service (e.g.,
/api→ Gunicorn,/static→ static web files). - Load balances between multiple backend instances.
- Caches responses (e.g., static images, JSON).
- Shields backend servers from direct exposure to the internet (security).
The Docker Compose command in Step 2 builds an NGINX image using the following Dockerfile. The image is then used to create the NGINX web server container that will be assigned the role of a reverse proxy: Dockerfile.nginx
You should now be able to access the Nginx reverse proxy which is your web server, using HTTPS via https://127.0.0.1/ or https://localhost/.
