Logo

How I deploy Python/Django Apps as a Solo Dev

Stop wrestling with complex deployments and learn how I use Ansible, Docker, and Litestream to run Django apps on a secure, self-updating, and ridiculously resilient setup.

Published: August 24, 2025

I’ve been deploying web applications for a while, and if there’s one thing I’ve learned, it’s that the “right way” to do it is always changing. Over the years, I’ve refined my process, and I think I’ve finally landed on a pattern that hits the sweet spot: it’s modern, incredibly resilient, easy to automate, and surprisingly simple to manage as a solo developer.

Today, I want to walk you through the setup I use for my Django projects. My goal is to have a fully automated pipeline that takes a bare Ubuntu server and turns it into a hardened, production-ready host for a web application.

Let’s dive in.

The Foundation: Automation with Ansible

First things first: I automate everything. Manually configuring a server is a recipe for mistakes and makes it impossible to replicate the setup consistently. For this, my tool of choice is Ansible.

Ansible lets me define my entire server configuration in code. I break down the setup into logical “roles”:

  • packages: This role makes sure all the basic dependencies are installed and the server is fully up-to-date.
  • basics: This handles the essentials like setting the server’s hostname, creating a dedicated admin user (with sudo rights), and setting up my SSH key for secure access. It even configures a small daily job to email me if there are new package updates available.
  • security: This is a big one. It hardens the server by setting up a firewall (ufw), installing fail2ban to automatically block malicious IPs, and locking down the SSH configuration to prevent unauthorized access.
  • django: This is where the magic happens. This role handles everything related to deploying my actual application.

Using Ansible means I can provision a new server from scratch, or update an existing one, by running a single command. It’s idempotent, which is a fancy way of saying I can run it a hundred times, and it will only make changes if something is out of sync with my configuration.

The Application Stack: Containerized with Docker

My entire Django application stack runs inside Docker containers, orchestrated with a compose.yml file. This is a game-changer for a few reasons: it isolates my application from the host system, makes dependencies predictable, and simplifies updates.

My typical compose.yml defines a few key services:

  1. web: This is the Django application itself, served by Gunicorn.
  2. worker: For any background tasks, I use Huey, which runs in its own dedicated container.
  3. caddy: My reverse proxy. More on this in a second.
  4. litestream: This is my secret weapon for database backups.

The Easiest HTTPS Ever: Caddy Server

For years, managing SSL/TLS certificates was a tedious chore. Caddy has completely solved this for me.

Caddy is a modern web server that I use as a reverse proxy. It sits in front of my Django container and directs traffic to it. But its killer feature is automatic HTTPS. I just put my domain name in its configuration file (the Caddyfile), and it automatically obtains and renews SSL certificates from Let’s Encrypt. No cron jobs, no scripts, no hassle. It just works.

Making SQLite Production-Ready with Litestream

Okay, this is the part of the stack I’m most excited about. I’m a huge fan of SQLite. It’s simple, fast, and built right into Python. For a huge number of applications, it’s all you really need. The only historical downside has been backups.

This is where Litestream comes in.

Litestream is a fantastic open-source tool that runs as a separate container in my stack. It continuously captures changes from my SQLite database file and streams them to an S3-compatible object storage bucket in real-time. I use Backblaze B2, which is incredibly affordable.

This gives me a live, up-to-the-second backup of my entire database. If my server ever goes down, I can bring up a new one, restore the database from my backup with a single command, and be back online in minutes. It gives me the resilience of a much more complex database setup, but with the beautiful simplicity of SQLite.

Putting It All Together: The Workflow

So, what does this look like in practice?

  1. I define all my server settings and secrets (like API keys) in configuration files, which I encrypt using Ansible Vault. This keeps my sensitive data safe, even in a private Git repository.
  2. I use a simple justfile to create shortcuts for my most common Ansible commands. Running just from my terminal kicks off the entire playbook.
  3. Ansible connects to my server, runs through each role, and configures everything. It sets up the firewall, installs Docker, and sets up various configurations files needed to run my application.

The only manual step I take is actually pushing my Docker image to a repository, and then running docker compose pull && docker compose up -d on the server. I actually don’t log into the server to run these commands. Instead, I use an Ansible ad-hoc command to do it from my local computer:

  ansible \
    -i ansible/hosts all \
    -m ansible.builtin.shell \
    -a "docker compose -f /root/compose.yml pull && docker compose -f /root/compose.yml up -d" \
    --become \
    --become-password-file /home/me/projects/my-project/ansible/.become \
    --vault-password-file /home/me/projects/my-project/ansible/.vault

Notice that I also store my root and vault passwords in files so that I don’t have to type them out. It’s a bad idea from a security perspective, but hey, life is simple as a solo dev.

And that’s it! In a few minutes, I have a fully configured, secure, and production-ready server running my Django application with automated HTTPS and real-time database backups.

This setup has been incredibly stable and has simplified my deployment process immensely. If you’re looking for a modern, robust way to deploy your Django projects, I highly recommend giving this pattern a try.

See an example of such a setup in this Github repository.

💡 Need a Developer Who Gets It Done?

If this post helped solve your problem, imagine what we could build together! I'm a full-stack developer with expertise in Python, Django, Typescript, and modern web technologies. I specialize in turning complex ideas into clean, efficient solutions.