Create Qcalc Prod Server on Linux
qCalc — Dockerless, Ubuntu Linux, VPS: Installation Guide
This guide covers a bare-metal (no Docker) deployment of qCalc on an Ubuntu VPS using: Python 3.12 · Django 5 · Gunicorn · PostgreSQL/MySQL · Memcached · Nginx · Certbot (Let's Encrypt)
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 Python 3.12
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
sudo apt install python3.12 python3.12-venv python3.12-dev -y
Verify:
python3.12 --version
3. Install System Dependencies
# Required by several Python packages (OpenCV, image processing)
sudo apt install -y libgl1-mesa-glx libglib2.0-0
# Build tools (required if using mysqlclient)
# sudo apt install build-essential pkg-config libmysqlclient-dev -y
# Git
sudo apt install git -y
4. Clone the qCalc Repository from Git
cd ~
git clone https://github.com/qcalc/qcalc.git qcalc_dock
cd ~/qcalc_dock/qcalc
If you are transferring files manually (e.g. via scp or rsync), ensure the full project structure is present:
~/qcalc_dock/
qcalc/ # Django project root
qcalc_res/ # Resource files (json, help, model)
5. Create the Python Virtual Environment
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install gunicorn
6. Create the Project Directory Structure
mkdir -p ~/qcalc_dock/.local/nginx/conf
mkdir -p ~/qcalc_dock/.local/nginx/templates
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/.cache
mkdir -p ~/qcalc_dock/qcalc_res
7. Copy Configuration Templates
From ~/qcalc_dock/qcalc/:
cp setup/env/template_setup.env setup.env
cp setup/env/template_prod.env .setup/prod.env
cp setup/env/template_gpref.json gpref.json
8. Install Database Service
8a. Install PostgreSQL
sudo apt install postgresql postgresql-contrib -y
sudo systemctl enable postgresql
sudo systemctl start postgresql
Create the database and user:
sudo -u postgres psql
CREATE DATABASE qcalc;
CREATE USER admin WITH PASSWORD '<your_secure_password>';
GRANT ALL PRIVILEGES ON DATABASE qcalc TO admin;
Install the PostgreSQL dependency, from ~/qcalc_dock/qcalc/:
source .venv/bin/activate
pip install psycopg2-binary==2.9.9
Replace
your_secure_passwordwith a strong password. Record it — you will need it later.
8b. (Optional) Install MySQL instead of PostgreSQL
sudo apt install mysql-server -y
sudo systemctl enable mysql
sudo systemctl start mysql
sudo mysql_secure_installation
Create the database and user:
sudo mysql -u root -p
CREATE DATABASE qcalc;
CREATE USER 'admin'@'localhost' IDENTIFIED BY '<your_secure_password>';
GRANT ALL PRIVILEGES ON qcalc.* TO 'admin'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Install the Python MySQL client (also install the build deps from step 3 if not already done):
sudo apt install build-essential pkg-config libmysqlclient-dev -y
cd ~/qcalc_dock/qcalc
source .venv/bin/activate
pip install mysqlclient==2.2.1
8c. Edit .setup/prod.env to Update Database Environment
From ~/qcalc_dock/qcalc/:
nano .setup/prod.env
If you have installed MySQL:
DB_ENGINE="django.db.backends.mysql"
DB_NAME="qcalc"
DB_USER="admin"
DB_PASSWORD="<your_secure_password>"
DB_HOST="127.0.0.1"
DB_PORT="3306"
If you have installed PostgreSQL:
DB_ENGINE="django.db.backends.postgresql_psycopg2"
DB_NAME="qcalc"
DB_USER="admin"
DB_PASSWORD="<your_secure_password>"
DB_HOST="127.0.0.1"
DB_PORT="5432"
Install Caching Service
9a. Install Memcached
sudo apt install memcached libmemcached-tools -y
sudo systemctl enable memcached
sudo systemctl start memcached
Memcached listens on 127.0.0.1:11211 by default. Edit /etc/memcached.conf to set the cache size (e.g. -m 256 for 256 MB).
9b. (Optional) Install Redis instead of Memcached
sudo apt install redis-server -y
sudo systemctl enable redis-server
sudo systemctl start redis-server
Verify Redis is responding:
redis-cli ping
# Expected output: PONG
9c. Edit .setup/prod.env to Update Caching Environment:
If you have installed Memcached:
DEFAULT_CACHE_ALIAS="memcached"
MEMCACHE_HOST="127.0.0.1"
MEMCACHE_PORT="11211"
If you have installed Redis:
DEFAULT_CACHE_ALIAS="redis"
REDIS_PUBSUB="1"
REDIS_HOST="127.0.0.1"
REDIS_PORT="6379"
REDIS_DB="1"
10. Install Nginx
sudo apt install nginx -y
sudo systemctl enable nginx
11. Install Certbot
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
10. Configure Environment Files
10a. ~/qcalc_dock/qcalc/setup.env
Copy the template and edit it:
nano ~/qcalc_dock/qcalc/setup.env
Set production values:
QCALC_SCHEME='https'
QCALC_DOMAIN="<yourdomain.com>"
QCALC_ENV_FILE=".setup/prod.env"
DJANGO_SETTINGS_MODULE="config.settings.prd"
10b. Production .setup/prod.env File
nano ~/qcalc_dock/qcalc/.setup/prod.env
Edit rest of the environment variables as appropriate (see setup/env/template_all_env_settings.env for full reference):
ROBOTS_TXT="robots.prod.txt"
DJANGO_DEBUG="False"
DJANGO_SECRET_KEY="<generate with: python -c 'import secrets; print(secrets.token_urlsafe(50))'>"
# Keep the Databse Environment Variables as we edited before
# Keep the Caching Environment Variables as we edited before
FILE_UPLOAD_TEMP_DIR="/home/<user_id>/qcalc_dock/.temp/"
JSON_FILES_DIR="/home/<user_id>/qcalc_dock/qcalc_res/json/"
HELP_FILES_DIR="/home/<user_id>/qcalc_dock/qcalc_res/help/"
DOCS_FILES_DIR="/home/<user_id>/qcalc_dock/qcalc_res/docs/"
AI_MODELS_DIR="/home/<user_id>/qcalc_dock/qcalc_res/model/"
# Optional API keys
FIXER_API_KEY="<your_fixer_api_key>"
OPENW_API_KEY="<your_openweather_api_key>"
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>"
10c. Edit Global Preferences gpref.json
You can keep the file as it is for time being.
nano ~/qcalc_dock/qcalc/gpref.json
11. Initialize Django
If you are not already in virtual environment
cd ~/qcalc_dock/qcalc
source .venv/bin/activate
python manage.py migrate
python manage.py collectstatic --noinput
# Create superuser
export DJANGO_SUPERUSER_USERNAME=super
export DJANGO_SUPERUSER_EMAIL=<admin@yourdomain.com>
export DJANGO_SUPERUSER_PASSWORD=super # change this now or immediately after first login
python manage.py createsuperuser --noinput
12. Configure Gunicorn as a systemd Service
Create a systemd unit file that mirrors the three-instance setup used in Docker:
sudo nano /etc/systemd/system/qcalc.service
Replace -- workers 4 parameter and number of instances.
If you want a stable low load in-house production environment you can keep just 1 instance and 2 workers.
[Unit]
Description=qCalc Gunicorn workers
After=network.target postgresql.service memcached.service
[Service]
User=<user_id>
Group=<user_id>
WorkingDirectory=/home/<user_id>/qcalc_dock/qcalc
Environment="PATH=/home/<user_id>/qcalc_dock/qcalc/.venv/bin"
ExecStart=/bin/bash -c '\
export GUNICORN_INSTANCE_ID=instance_1; \
/home/<user_id>/qcalc_dock/qcalc/.venv/bin/gunicorn config.wsgi:application \
--bind 127.0.0.1:8001 --workers 4 --timeout 900 \
--access-logfile /home/<user_id>/qcalc_dock/.local/log/gunicorn/access_1.log \
--error-logfile /home/<user_id>/qcalc_dock/.local/log/gunicorn/error_1.log \
--log-file /home/<user_id>/qcalc_dock/.local/log/gunicorn/qcalc_1.log & \
export GUNICORN_INSTANCE_ID=instance_2; \
/home/<user_id>/qcalc_dock/qcalc/.venv/bin/gunicorn config.wsgi:application \
--bind 127.0.0.1:8002 --workers 4 --timeout 900 \
--access-logfile /home/<user_id>/qcalc_dock/.local/log/gunicorn/access_2.log \
--error-logfile /home/<user_id>/qcalc_dock/.local/log/gunicorn/error_2.log \
--log-file /home/<user_id>/qcalc_dock/.local/log/gunicorn/qcalc_2.log & \
export GUNICORN_INSTANCE_ID=instance_3; \
/home/<user_id>/qcalc_dock/qcalc/.venv/bin/gunicorn config.wsgi:application \
--bind 127.0.0.1:8003 --workers 4 --timeout 900 \
--access-logfile /home/<user_id>/qcalc_dock/.local/log/gunicorn/access_3.log \
--error-logfile /home/<user_id>/qcalc_dock/.local/log/gunicorn/error_3.log \
--log-file /home/<user_id>/qcalc_dock/.local/log/gunicorn/qcalc_3.log & \
wait'
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable qcalc
sudo systemctl start qcalc
sudo systemctl status qcalc
13. Configure Nginx
13a. Main nginx.conf
Base your config on setup/nginx/template_nginx.conf-v1.4j.conf. Copy it to /etc/nginx/nginx.conf:
sudo cp ~/qcalc_dock/qcalc/setup/nginx/template_nginx.conf-v1.4j.conf /etc/nginx/nginx.conf
Review and adjust worker_processes, worker_connections, and client_max_body_size as needed.
13b. Initial HTTP-Only Site (Before SSL Certificate)
Before obtaining the SSL certificate, create a minimal HTTP config so Certbot can complete the ACME challenge. Based on setup/nginx/template_default.conf.init:
sudo nano /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
sudo mkdir -p /var/www/certbot
sudo nginx -t
sudo systemctl reload nginx
14. Obtain the SSL Certificate
sudo certbot certonly --webroot \
--webroot-path=/var/www/certbot \
--email you@yourdomain.com \
--agree-tos --no-eff-email \
--cert-name yourdomain.com \
-d yourdomain.com -d www.yourdomain.com
Certificates are written to /etc/letsencrypt/live/yourdomain.com/.
Set up automatic renewal:
sudo systemctl enable snap.certbot.renew.timer
15. Configure Nginx for HTTPS
Based on setup/nginx/template_default.conf.template-v1.8j.conf. Replace the HTTP-only config with the full production config. Adapt the upstream block and all path/domain placeholders:
sudo nano /etc/nginx/conf.d/default.conf
Replace every occurrence of ${NGINX_HOST} with yourdomain.com and replace the Docker upstream hostnames (qcalc:800x) with 127.0.0.1:800x.
Key changes from the template for a non-Docker setup:
# Upstream — use localhost instead of Docker container name
upstream qcalc_servers {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
}
# Static files — use the local filesystem path
location ^~ /static/ {
alias /home/ubuntu/qcalc_dock/qcalc/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# SSL certificate paths
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Log files
access_log /home/ubuntu/qcalc_dock/.local/log/nginx/access.log;
Validate and reload:
sudo nginx -t
sudo systemctl reload nginx
16. File Permissions
Ensure Nginx can read static files:
chmod o+x /home/ubuntu
chmod -R o+r /home/ubuntu/qcalc_dock/qcalc/staticfiles/
17. Verify the Installation
# Check all services are running
sudo systemctl status qcalc
sudo systemctl status nginx
sudo systemctl status postgresql
sudo systemctl status memcached
# Tail logs for errors
tail -f ~/qcalc_dock/.local/log/gunicorn/error_1.log
tail -f ~/qcalc_dock/.local/log/nginx/access.log
Open https://yourdomain.com in a browser to confirm the site is live.
18. Post-Installation
- Log in to the Django admin at
https://yourdomain.com/admin/with usernamesuperand immediately change the password. - Replace the placeholder API keys in
.setup/prod.env(Fixer.io, OpenWeather, OpenAI, etc.) with your own keys. - Review
robots.txt— copy and rename a template fromqcalc/qsite/static/txt/to matchROBOTS_TXTin your.env.
Directory Layout Summary
~/qcalc_dock/ # qCalc PROJECT dir (Git-managed)
├── qcalc/ # Django project, qCalc ROOT dir
│ ├── .venv/ # Python virtual environment
│ ├── 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
│ └── log/
│ ├── gunicorn/
│ └── nginx/
└── .temp/ # Temporary file uploads, # not in Git
Quick Reference — Useful Commands
| Task | Command |
|---|---|
| Restart qcalc | sudo systemctl restart qcalc |
| Reload Nginx | sudo systemctl reload nginx |
| Activate venv | source ~/qcalc_dock/qcalc/.venv/bin/activate |
| Run migrations | python manage.py migrate |
| Collect static | python manage.py collectstatic --noinput |
| Renew certificate | sudo certbot renew |
| View Gunicorn log | tail -f ~/qcalc_dock/.local/log/gunicorn/error_1.log |
| View Nginx log | tail -f ~/qcalc_dock/.local/log/nginx/access.log |