Create Qcalc Prod Server on Docker

qCalc — Docker, Ubuntu Linux, VPS: Installation Guide

This guide covers a Docker-based deployment of qCalc on an Ubuntu VPS. All services (qCalc/Gunicorn, Nginx, Certbot, PostgreSQL, Memcached) run as Docker containers defined in setup/docker/template_docker.yml.


Prerequisites

  • Ubuntu 22.04 LTS or 24.04 LTS VPS with root / sudo access
  • A registered domain name pointed at the VPS IP address
  • SSH access to the server

1. Initial Linux Server Setup

Follow Initial Linux Server Setup if you do not have a user account in linux

2. Install Docker Engine and Docker Compose

Following instruction set is for Ubuntu 26.04 LTS.

sudo apt update
sudo apt install -y ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
    -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update

sudo apt install -y \
    docker-ce \
    docker-ce-cli \
    containerd.io \
    docker-buildx-plugin \
    docker-compose-plugin

sudo systemctl enable --now docker

sudo usermod -aG docker ${USER}
newgrp docker

docker info
docker compose version

3. Clone the qCalc Repository from Git

cd ~
git clone https://github.com/qcalc/qcalc.git qcalc_dock

4. Create the Project Directory Structure

mkdir -p ~/qcalc_dock/.local/nginx/templates
mkdir -p ~/qcalc_dock/.local/nginx/conf
mkdir -p ~/qcalc_dock/.local/certbot/conf
mkdir -p ~/qcalc_dock/.local/certbot/www
mkdir -p ~/qcalc_dock/.local/log/nginx
mkdir -p ~/qcalc_dock/.local/log/gunicorn
mkdir -p ~/qcalc_dock/.local/log/certbot
mkdir -p ~/qcalc_dock/.temp
mkdir -p ~/qcalc_dock/qcalc/.setup

5. Set Up the Docker Files

The compose template is at qcalc/setup/docker/template_docker.yml. Copy it one level up as docker-compose.yml:

cp ~/qcalc_dock/qcalc/setup/docker/template_docker_compose.yml ~/qcalc_dock/docker-compose.yml
cp ~/qcalc_dock/qcalc/setup/docker/template_dockerfile ~/qcalc_dock/Dockerfile
cp ~/qcalc_dock/qcalc/setup/docker/.dockerignore ~/qcalc_dock/.dockerignore

The compose file uses ~/qcalc_dock/.local/ paths for all volumes. No edits are required unless you change the installation directory.


6. Set Up Nginx Configuration Files

6a. Main nginx.conf

cp ~/qcalc_dock/qcalc/setup/nginx/template_nginx.conf-v1.4j.conf \
   ~/qcalc_dock/.local/nginx/nginx.conf

6b. Initial HTTP-only config (used before the SSL certificate exists)

cp ~/qcalc_dock/qcalc/setup/nginx/template_default.conf.init \
   ~/qcalc_dock/.local/nginx/conf/default.conf.init

Edit the file and replace <replace_with_your_domain> with your actual domain:

nano ~/qcalc_dock/.local/nginx/conf/default.conf.init

6c. Full HTTPS template (used after the SSL certificate is obtained)

cp ~/qcalc_dock/qcalc/setup/nginx/template_default.conf.template-v1.8j.conf \
   ~/qcalc_dock/.local/nginx/templates/default.conf.template.off

The .off suffix prevents Nginx from processing this template before the SSL certificate exists. ${NGINX_HOST} placeholders in this file are automatically replaced by the Nginx Docker container using the NGINX_HOST environment variable set in docker-compose.yml.


7. Configure Environment Files

7a. setup.env

cp ~/qcalc_dock/qcalc/setup/env/template_setup.env ~/qcalc_dock/qcalc/setup.env
nano ~/qcalc_dock/qcalc/setup.env
QCALC_SCHEME='https'
QCALC_DOMAIN="<yourdomain.com>"
QCALC_ENV_FILE=".setup/prod.env"
DJANGO_SETTINGS_MODULE="config.settings.prd"

7b. Production .env file

cp ~/qcalc_dock/qcalc/setup/env/template_prod.env ~/qcalc_dock/qcalc/.setup/prod.env
nano ~/qcalc_dock/qcalc/.setup/prod.env

Key settings to fill in (see setup/env/template_all_env_settings.env for the full reference): Set the DJANGO_SECRET_KEY value after generating the key using the command mentioned below.

ROBOTS_TXT="robots.prod.txt"
DJANGO_DEBUG="False"
DJANGO_SECRET_KEY="<generate: python -c 'import secrets; print(secrets.token_urlsafe(50))'>"

DB_ENGINE="django.db.backends.postgresql_psycopg2"
DB_NAME="qcalc"
DB_USER="postgres"
DB_PASSWORD="postgres"       # matches POSTGRES_PASSWORD in docker-compose.yml
DB_HOST="postgres"           # Docker service/container name, not localhost
DB_PORT="5432"

DEFAULT_CACHE_ALIAS="memcached"
MEMCACHE_HOST="memcached"    # Docker service/container name
MEMCACHE_PORT="11211"

# Paths inside the qcalc container (must match docker-compose.yml volumes)
FILE_UPLOAD_TEMP_DIR="/usr/src/qcalc_dock/.temp/"
JSON_FILES_DIR="/usr/src/qcalc_dock/qcalc_res/json/"
HELP_FILES_DIR="/usr/src/qcalc_dock/qcalc_res/help/"
DOCS_FILES_DIR="/usr/src/qcalc_dock/qcalc_res/docs/"
AI_MODELS_DIR="/usr/src/qcalc_dock/qcalc_res/model/"

FIXER_API_KEY="<your_fixer_api_key>"        # obtain an API key for currency rates update
OPENW_API_KEY="<your_openweather_api_key>"  # this is optional

DJANGO_EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
DJANGO_EMAIL_HOST="<smtp.yourmailprovider.com>"
DJANGO_EMAIL_PORT="587"
DJANGO_EMAIL_USE_TLS="True"
DJANGO_EMAIL_HOST_USER="<you@yourdomain.com>"
DJANGO_EMAIL_HOST_PASSWORD="<your_email_password>"

Important: DB_HOST and MEMCACHE_HOST must be the Docker container/service names (postgres, memcached), not localhost.

7c. gpref.json

cp ~/qcalc_dock/qcalc/setup/env/template_gpref.json ~/qcalc_dock/qcalc/gpref.json

8. Create Docker Named Volumes

docker volume create static

or if you have opted for postgres

docker volume create pgdata

or if you have opted for mysql

docker volume create mysqldata

9. Phase 1 — Start Containers with HTTP-Only Nginx

Activate the HTTP-only Nginx config so Certbot can complete the ACME challenge:

# Uses cp_conf_init.sh logic — copy init config into active default.conf
cp ~/qcalc_dock/.local/nginx/conf/default.conf.init \
   ~/qcalc_dock/.local/nginx/conf/default.conf
cd ~/qcalc_dock
docker compose up

10. Obtain the SSL Certificate

Run Certbot inside the certbot container to issue the certificate via the webroot method:

docker exec -it certbot certbot certonly \
  --webroot --webroot-path=/var/www/certbot \
  --email <your_email_id> \
  --agree-tos --no-eff-email \
  --cert-name <your_domain> \
  -d <your_domainm> -d www.<your_domain>

Expected output confirms certificate paths:

Certificate is saved at: /etc/letsencrypt/live/yourdomain.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/yourdomain.com/privkey.pem

On the host these map to ~/qcalc_dock/.local/certbot/conf/live/yourdomain.com/.


11. Phase 2 — Switch Nginx to HTTPS

Remove the HTTP-only config and activate the full HTTPS template:

mv ~/qcalc_dock/qcalc_res/nginx/conf/default.conf \
   ~/qcalc_dock/qcalc_res/nginx/conf/default.conf.init
mv ~/qcalc_dock/qcalc_res/nginx/templates/default.conf.template.off \
   ~/qcalc_dock/qcalc_res/nginx/templates/default.conf.template
cd ~/qcalc_dock

The Nginx container automatically processes default.conf.template, substitutes ${NGINX_HOST} with yourdomain.com, and writes the result to conf.d/default.conf.

Restart the containers to pick up the new config:

cd ~/qcalc_dock
docker compose restart

Open https://<your_domain> in a browser to verify the site is live and the certificate is valid.


12. Post-Installation

  • Log in to the Django admin at https://<your_domain>/admin/ with the superuser created during collectstatic/migrate (the qCalc container runs these automatically on startup per docker-compose.yml). The default credentials are super / superchange the password immediately.
  • Replace placeholder API keys in .setup/prod.env (Fixer.io, OpenWeather, OpenAI, etc.).
  • Review robots.prod.txt — copy and rename a template from qcalc/qsite/static/txt/ to match ROBOTS_TXT in your ~qcalc_dock/qcalc/.setup/ .env file.
cp ~/qcalc_dock/qcalc/qsite/static/txt/template_robots.prod.txt \
   ~/qcalc_dock/qcalc/qsite/static/txt/robots.prod.txt

You verify it using the url: https://<your_domain>/robots.txt


Day-to-Day Operations

Pull latest code and redeploy

cd ~/qcalc_dock/
git pull
docker compose restart qcalc

Verify static files inside the container

docker exec -it qcalc python manage.py findstatic js/qcalc.js

Run a management command inside the container

docker exec -it qcalc python manage.py <command>

View logs

# Gunicorn
tail -f ~/qcalc_dock/.local/log/gunicorn/error_1.log

# Nginx
tail -f ~/qcalc_dock/.local/log/nginx/access.log

# Container stdout
docker compose logs -f qcalc
docker compose logs -f nginx

Quick Reference — Useful Commands

Task Command
Start all containers docker compose up -d
Stop all containers docker compose down
Restart a single service docker compose restart qcalc
Rebuild qcalc image docker compose build qcalc
Rebuild and restart docker compose up -d --build qcalc
Reset to HTTP-only nginx cp_conf_init.sh (or its inline equivalent in step 9)
Activate HTTPS nginx cp_conf_template.sh (or its inline equivalent in step 11)
Renew SSL certificate docker exec -it certbot certbot renew
Open a shell in container docker exec -it qcalc bash

Directory Layout Summary

~/qcalc_dock/                      # qCalc PROJECT dir (Git-managed)
├── docker-compose.yml             # Copied from qcalc/setup/docker/template_docker.yml
├── qcalc/                         # Django project, qCalc ROOT dir
│   ├── setup.env                  # Startup configuration (not in Git)
│   ├── .setup/                    # not in Git
│   │   └── prod.env               # Production secrets (not in Git)
│   └── qsite                      # qCalc APP dir
├── qcalc_res/                     # Resource files (json, help, model)
├── .local/                        # not in Git
│   ├── nginx/
│   │   ├── nginx.conf             # Nginx main config (host-mounted)
│   │   ├── conf/
│   │   │   ├── default.conf       # Active site config (switched per phase)
│   │   │   └── default.conf.init  # HTTP-only config (kept as backup)
│   │   └── templates/
│   │       ├── default.conf.template      # Active HTTPS template (Phase 2)
│   │       └── default.conf.template.off  # Inactive HTTPS template (Phase 1)
│   ├── certbot/
│   │   ├── conf/                  # Let's Encrypt certificates
│   │   └── www/                   # ACME challenge webroot
│   └── log/
│       ├── gunicorn/
│       ├── nginx/
│       └── certbot/
└──  .temp/                         # Temporary file uploads, not in Git