Deployment
The following example documents one way to deploy Comics. As Comics is a standard Django project with an additional batch job for crawling, it may be deployed in just about any way a Django project may be deployed. Please refer to Django’s deployment documentation for further details.
In the following examples we assume that we are deploying Comics at
http://comics.example.com/, using Nginx, Gunicorn, and PostgreSQL. The Django
application and batch job is both running as the user comics-user. The
static media files, like comic images, are served from
http://comics.example.com/static/.
Database
Comics should theoretically work with any database supported by Django. Though, development is mostly done on SQLite and PostgreSQL. For production use, PostgreSQL is the recommended choice.
Note
If you are going to use SQLite in a deployment with Nginx and so on, you need to ensure that the user the web server will be running as has write access to the directory the SQLite database file is located in.
Example .env
In the following examples, we assume the Comics source code is unpacked at
/srv/comics.example.com/app.
To change settings, you should not change the settings files shipped with
Comics, but instead override the settings using environment variables, or by
creating a file named /srv/comics.example.com/app/.env. You must
at least set DJANGO_SECRET_KEY and database settings, unless you use
SQLite.
A full set of environment variables for a production deployment may look like this:
DJANGO_SECRET_KEY=replace-this-with-a-long-random-value
DJANGO_CSRF_TRUSTED_ORIGINS=https://comics.example.com
DJANGO_DEFAULT_FROM_EMAIL=comics@example.com
# Sending email, alternative 1: Using a local SMTP server
DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
# Sending email, alternative 2: Using the Mailgun API (which has a free tier)
MAILGUN_API_KEY=your-mailgun-api-key
MAILGUN_API_URL=https://api.eu.mailgun.net/v3
MAILGUN_SENDER_DOMAIN=comics.example.com
DJANGO_MEDIA_ROOT=/srv/comics.example.com/htdocs/media/
DJANGO_MEDIA_URL=https://comics.example.com/media/
DJANGO_STATIC_ROOT=/srv/comics.example.com/htdocs/static/
DJANGO_STATIC_URL=https://comics.example.com/static/
DATABASE_URL=postgres://comics:topsecret-password@localhost:5432/comics
CACHE_URL=memcache://127.0.0.1:11211
COMICS_LOG_FILENAME=/srv/comics.example.com/log/comics.log
COMICS_SITE_TITLE=comics.example.com
COMICS_INVITE_MODE=true
Of course, you should change most, if not all, of these settings to fit your own installation.
If your are not running a memcached server, remove CACHE_URL variable
from your environment. Comics does not require a cache, but responses are
significantly faster with a cache available.
Example Gunicorn setup
Comics is a WSGI app and can be run with any WSGI server, for example Gunicorn. Gunicorn is a Python program, so you can simply install it in Comics’ own virtualenv:
cd /srv/comics.example.com/app
uv sync --extra server
Then you need to start Gunicorn, for example with a systemd service:
[Unit]
Description=comics
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
User=comics-user
Group=comics-user
Restart=always
WorkingDirectory=/srv/comics.example.com/app
ExecStart=uv run gunicorn --bind=127.0.0.1:8000 --workers=9 --access-logfile=/srv/comics.example.com/htlogs/gunicorn-access.log --error-logfile=/srv/comics.example.com/htlogs/gunicorn-error.log comics.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
Example Nginx vhost
The web server Nginx can be used in front of Gunicorn to terminate HTTPS connections and effectively serve static files.
The following is an example of a complete Nginx vhost:
server {
server_name comics.example.com;
listen 443 ssl http2;
listen [::]:443 ssl http2;
access_log /srv/comics.example.com/htlogs/nginx-access.log;
error_log /srv/comics.example.com/htlogs/nginx-error.log error;
ssl_certificate /etc/letsencrypt/live/comics.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/comics.example.com/privkey.pem;
location /media {
root /srv/comics.example.com/htdocs;
expires max;
}
location /static {
root /srv/comics.example.com/htdocs;
expires max;
location ~* \/fonts\/ {
add_header Access-Control-Allow-Origin *;
}
}
location / {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 10;
proxy_read_timeout 30;
proxy_pass http://localhost:8000/;
}
}
For details, please refer to the documentation of the Nginx project.
Collecting static files
When you’re not running in development mode, you’ll need to collect the static
files from all apps into the STATIC_ROOT. To do this, run:
uv run comics collectstatic
You have to rerun this command every time you deploy changes to graphics, CSS and JavaScript. For more details, see the Django documentation on staticfiles.
Example cronjob
To get new comics releases, you should run get_releases regularly. In
addition, you should run clearsessions to clear expired user sessions.
One way is to use cron e.g. by placing the following in
/etc/cron.d/comics:
MAILTO=comics@example.com
1 * * * * comics-user cd /srv/comics.example.com/app && uv run comics get_releases -v0
1 3 * * * comics-user cd /srv/comics.example.com/app && uv run comics clearsessions -v0
By setting MAILTO any exceptions raised by the comic crawlers will be sent
by mail to the given mail address. 1 * * * * specifies that the command
should be run 1 minute past every hour.