A small project planning tool designed specifically for weddings.
  • HTML 53.9%
  • Python 41.7%
  • JavaScript 2.7%
  • CSS 1.2%
  • Dockerfile 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-03-09 17:01:55 -06:00
backend Better flag admin and bypass RLS. 2026-03-09 16:34:12 -06:00
frontend Add roles to members and due date to projects. 2026-02-27 17:37:38 -07:00
infra Update root directive for media files. 2026-03-09 17:01:55 -06:00
.containerignore Fixup procrastinate image. 2026-02-14 06:55:11 -07:00
.gitignore Organize frontend code better. 2026-02-14 20:26:38 -07:00
.python-version Initial commit 2025-12-27 11:29:16 -07:00
manage.py Transition to (not yet functional) sign-up flow. 2026-01-22 05:47:05 -07:00
package-lock.json Fork response-targets to allow updating swap strategies. 2026-02-21 07:21:35 -07:00
package.json Fork response-targets to allow updating swap strategies. 2026-02-21 07:21:35 -07:00
pyproject.toml Append slash and fix tests. 2026-02-12 21:54:52 -07:00
README.md Drop firewalld requirement in favor of direct binding. 2026-03-06 07:45:28 -07:00
uv.lock Append slash and fix tests. 2026-02-12 21:54:52 -07:00

Overview

Development

The project is managed using podman.

Virtual Machine

If running on MacOS, Windows, or a Linux distro that ships without systemd (e.g. Void Linux), you'll need to install a podman virtual machine to continue. Even if this is not the case, it is recommended to use a VM anyways for both consistency and security.

Depending on your OS, you will need some hypervisor software installed. Refer to the podman-machine documentation for details. Once setup, run:

# Linux
$ podman machine init --volume "$PWD:/home/core/dovely" dovely
# MacOS
$ podman machine init --volume "$PWD:/var/home/core/dovely" dovely

and then

$ podman machine start dovely

In some cases, the above commands may complain about a few additional packages that may be missing.

The first that may be missing is gvproxy. Download the latest release for your architecture. You will want to rename the download to just gvproxy and set its executable bit on. Then move to one of:

  • /usr/local/libexec/podman
  • /usr/local/lib/podman
  • /usr/libexec/podman
  • /usr/lib/podman

or a custom location which you can specify in $XDG_CONFIG_HOME/containers/containers.conf like so:

[engine]
helper_binaries_dir=['/path/to/gvisor']

The second that may be missing is virtiofsd. Download the latest release and move the resulting binary somewhere in your PATH.

Quadlets

Podman provides a containerized systemd-like interface via quadlets. We use this functionality for services our Django server depend on, e.g. the database, cache, etc. To turn on, install the files in quadlets/ into the VM and start the services:

$ podman machine ssh dovely
> cd dovely
> podman quadlet install infra/dovely* infra/dev/dovely*
> systemctl --user start dovely-pod

By booting up just the pod, all containers associated with the pod will automatically turn on. Refer to the documentation for help on writing unit files.

Procrastinate

The dovely-procrastinate.container quadlet will continually fail until we create an image it can pull. First add the secrets the quadlet relies on, e.g.

$ printf "$VALUE" | podman secret create secret_key -

Then build the base image:

$ podman build --file infra/$MODE/Containerfile --tag django .

Note, we use the production image even in development. It gives a change to test the production Containerfile and the procrastinate instance should rarely need updating.

Environment

You will need to create a .env file locally with at the following contents:

DEBUG=1
POSTMARK_API_KEY=
SECRET_KEY=django-insecure-msa*o7ov4jw2+hmfib9-!#nm=_%n6=^29xt=*yf2l=4o5!fm6y
UMAMI_SCRIPT=http://localhost:3000/script.js
UMAMI_WEBSITE_ID=

Asset Management

Our frontend development is largely driven by the following libraries:

Before continuing, it is strongly recommended to install nvm. It is assumed you are running node v24.12.0 (npm v11.6.2). Do so by running:

$ nvm install 24.12.0
$ nvm use 24.12.0

You can produce the required CSS and JavaScript files by running:

$ npm install
$ npm run build

This will produce an index.css and index.js file in the backend/assets directory. Note the assets directory is specified as an extra search directory for static files in our Django application.

We also augmented the default Django template system with django-cotton which simplifies component construction. For ease of development, you will want to continually scan the template files for new class names. You can do so by running the following in parallel to the development server command:

$ npm run dev

The above may require other packages for watching files, e.g. watchman or similar.

Static Files

Static files are handled by Whitenoise. No additional configuration is necessary outside of running the collectstatic command at some point.

Media Files

For now, we just use the local filesystem to store media files. This is done using a volume named django-media, shared by nginx and our django instances.

Database

Before starting up the development server, you need to add the user the server will run as. SSH into the VM and then run:

$ podman exec -it systemd-dovely-postgres psql -U postgres
> CREATE USER django WITH PASSWORD 'django';
> GRANT CONNECT ON DATABASE dovely TO django;
> GRANT pg_read_all_data TO django;
> GRANT pg_write_all_data TO django;

Development Server

To run the Django development server, first build the image described by Containerfile. Then start a new container based on this image, attaching it to the systemd pod started up in the previous section. Assuming you are still SSH'ed into the VM, you can run:

$ infra/attach dev uv run manage.py runserver 0.0.0.0:8000

Once running, the development server can be accessed at http://localhost:8000.

Testing

To run tests, first SSH into the VM. Afterward, you can run the following:

$ infra/attach dev uv run manage.py collectstatic --noinput
$ infra/attach dev uv run manage.py test

Analytics

We use Umami for basic analytics. Access to the Umami dashboard by navigating to http://localhost:3000. The default username and password is admin and umami respectively.

When first starting, add a new website with form fields/values:

  • Name: dovely
  • Domain: http://localhost:8000

Afterward, update the UMAMI_WEBSITE_ID field in your local .env file with the newly created ID.

Deployment

We are hosted on Hetzner. This section assumes you are SSH'ed into the server instance and have run commands corresponding to the previous instructions (though not in a VM).

Unlike in development, our server runs in rootful mode. This ensures systemd processes are always running, simplifies certain volume mounting expectations, and in general makes our life a bit easier.

Our servers run Fedora since it comes pre-installed with a recent enough version of podman that supports quadlets. The first time you log into the machine, you will want to run the following commands:

$ sudo dnf update
$ sudo dnf install certbot git podman

You will also want to register an SSH key with GitHub so you can then clone this repository.


Many of the steps used to initially turn up the server mirror those in Development. Order doesn't strictly matter since quadlets automatically retry on failure, but following the remaining sections in order is probably most seamless.

Ignore instructions related to the .env file. This is not used in production.

Django Server

We deploy our Django server as a quadlet in production. First, add the necessary secrets, one for each environment variable the application expects to have available.

This also installs the necessary secrets for our procrastinate worker since those are a subset of the server's secrets.

Then build a new image and install the corresponding quadlet along with all the others:

$ podman build --file infra/$MODE/Containerfile --tag django .
$ podman quadlet install infra/dovely* infra/$MODE/dovely*
$ systemctl restart dovely-pod

Database

Follow the instructions in the previous Database step.

You should also run any pending migrations like so:

$ infra/attach $MODE uv run manage.py migrate

Analytics

Follow the instructions in the previous Analytics step.

Reverse Proxy

We use the NGINX reverse proxy to serve multiple backends through a single port, namely the Django backend and Umami. To configure the proxy, run the following:

$ podman cp infra/$MODE/nginx.conf systemd-dovely-nginx:/etc/nginx/conf.d/dovely.conf
$ systemctl restart dovely-nginx

Use the $MODE/nginx.bootstrap.conf file when first bootstrapping the server. Update to the environment specific configuration once certbot is setup.

SSL Certificates

We employ Let's Encrypt to issue us SSL certificates. Using email josh@getdovely.com, generate new certificates by running the following:

$ mkdir -p $HOME/.local/state/data
$ certbot certonly --webroot \
    --webroot-path $HOME/.local/state/data \
    --domain getdovely.com \
    --domain www.getdovely.com \
    --domain umami.getdovely.com

This will generate a few certificates we can now copy into our bound volume:

$ cp /etc/letsencrypt/live/getdovely.com/fullchain.pem /root/.local/state/cert
$ cp /etc/letsencrypt/live/getdovely.com/privkey.pem /root/.local/state/cert

You can renew certificates with just the following:

$ certbot renew

Copy the new live certificates into the same location as above and restart the NGINX server:

$ systemctl restart dovely-nginx