Rebuilding My Website for the Third Time

Quick Version Recap

  • Version 1
    The first version of mykytaso.com was a simple webpage hosted on a Raspberry Pi 4 and exposed from my home network via a Cloudflare Tunnel.


  • Version 2
    The second iteration was a Django application deployed on AWS, where I implemented a block-based editor for creating posts (similar to Notion blocks).
    GitHub Repo

  • Version 3 (current)
    For the third version, I fully rewrote the codebase. It's still built with Django, but now includes many new features and is deployed on Hetzner instead of AWS.
    GitHub Repo

Tech Stack

  • Backend: Django 6.0+ · Python 3.12+ · PostgreSQL
  • Frontend: HTML · CSS + BEM · HTMX · Vanilla JS · Django Templates
  • Infrastructure: Docker · Gunicorn + Uvicorn · Nginx · Hetzner · Sentry
  • Tools: GitHub Actions CI/CD · uv · Ruff · Mistune + Pygments · Mailgun API

CI/CD

GitHub Actions runs a two-stage pipeline (build and deploy) on pushes to main.

Build job

  • Builds the Docker image.
  • Tags it with latest and $GITHUB_SHA.
  • Pushes the image to ghcr.io.

Deploy job

  • Generates .env from GitHub secrets.
  • Uploads config files to the production server via SCP.
  • SSHes into the server to pull the image and run docker compose up -d.

Database Design

  • UUID primary keys.
  • Soft deletes for users to preserve data integrity.
  • Database-level constraints to prevent duplicate likes.
  • Indexes on frequently queried fields.
  • Composite indexes for multi-field lookups.
  • Cached rendered HTML to speed up Markdown rendering.

PostgreSQL Backup System

I set up an automated daily PostgreSQL backup system on my home server (a 2014 Mac mini running Ubuntu Server).

A Python script, scheduled with cron, does the following:

  • Connects to the production server.
  • Creates a PostgreSQL backup with pg_dump.
  • Copies the backup file to the home server and removes it from the production server.
  • Deletes backups older than 7 days.
  • Sends Telegram notifications for both successful runs and errors.

Email Sending

I’ve integrated the Mailgun API to handle:

  • Email confirmations for registrations and email changes.
  • Password reset emails.

Frontend Architecture

  • Although I’m primarily a backend developer, I chose pure CSS with BEM over frameworks.
  • Mobile and desktop layouts.
  • HTMX-driven interactions.
  • Dark and light themes with localStorage persistence.
  • Client-side time zone conversion.
  • Image zoom with Lightense.

Security and Monitoring

  • Nginx is configured to trust Cloudflare IP ranges and extract the real client IP.
  • Nginx uses Cloudflare Origin Certificates to encrypt traffic between Cloudflare and the origin server.
  • Nginx rate limiting.
  • Email verification and password reset tokens.
  • Strong password hashing and validation.
  • Early bot detection implemented via a hidden honeypot field.
  • reCAPTCHA v2 on auth endpoints.
  • Separate environments for development and production.
  • Sentry error tracking.
  • Custom request logging middleware.

Posts

  • Markdown rendering via a custom Mistune renderer.
  • Pygments syntax highlighting for code blocks.
  • Stores rendered Markdown HTML in the database, clearing it only when content changes.
  • Supports both auto-rendered Markdown and pre-rendered HTML.
  • Auto-generated, URL-friendly slugs with uniqueness guarantees.
  • OpenGraph metadata for SEO.

Engagement

  • Likes for both authenticated users and anonymous visitors.
  • One-like-per-user/IP enforced at the database level.
  • View counting and visibility controls.
  • Comments for authenticated users.

SEO

Comments (2)
Kekus

Good job!

 
Mykyta

Kekus, thanks!