With Growth Comes Ownership: Rethinking How We Manage Our CFP Process

Ashish Padhy & Thulisile Sibanda

·11 min read

What starts as a workaround often becomes a turning point, especially when growth is involved. We have been running the AsyncAPI Conference brand for years (including the online editions), but we haven’t had a very stable system or process. We were content at the time until we reached a moment where it was no longer enough. We want sustainability, control, and a process that properly reflects how the brand should be run; hence, we steered towards this goal.

The Back Story and Need

The idea of actually establishing our own CFP system has been there in the background for a while. Each time we planned a conference, there was a cycle that brought the same friction: limited flexibility and, mostly, a lack of consistency. The process involved multiple manual steps and frequent back-and-forth with organisers. Reviewer coordination was another pain point, with shared spreadsheets often falling out of sync, making it hard to track feedback or avoid overlap. We found ourselves adjusting to these workflows as long as they just worked, but over time, it became clear that the issue wasn’t the way we did things; we didn’t actually have any ownership.

In fact, growth had forced our hand and prompted us to think sustainably, because as we grew and expanded our reach, so did the complexity of managing submissions. Relying on external support systems sometimes made it harder to plan ahead, manage shared information, and even create a better experience for our speakers. We needed something that scaled with us, hence we started exploring how other Open Source community conferences managed theirs and if there were any Open Source solutions out there.

When Ideas Become Reality

We didn’t want to create another project that needed maintenance, but something that's already established, easy to set up, deploy and Open Source friendly. Based on research, we had three options, so we started a GitHub discussion to gain better insights from the community and determine whether others had more expertise with each platform.

In the end, we opted to use Pretalx as our conference management software and deploy it on DigitalOcean.

The Architecture

The technical goal was to keep each system responsible for one job:

  • Pretalx manages CFPs, speakers, reviews, accepted talks, schedules, and uploaded media.
  • DigitalOcean runs the Pretalx production stack.
  • GitHub Actions deploys Pretalx and syncs public conference data.
  • The conference website renders venue pages, city lists, CFP states, speaker lists, and agenda sections from generated data.
  • Netlify continues to serve the public conference website.

The important design decision is that the website is not the source of truth for CFP operations. Pretalx owns the event workflow. The website consumes a generated snapshot of the public data.

That gives us a static, predictable website build while still letting organisers work in a proper conference management system.

Pretalx on DigitalOcean

We deployed Pretalx on a DigitalOcean Droplet using Docker Compose. The stack is intentionally small:

  • pretalx: custom Pretalx image with the AsyncAPI CFP plugin installed.
  • db: PostgreSQL 15.
  • redis: Redis for sessions, cache, and Celery.
  • nginx: internal container for /media/..., /static/..., and reverse proxying to Pretalx.
  • caddy: host-level reverse proxy for HTTPS and public traffic.

The public request path looks like this:

1Browser
2  -> https://cfp.asyncapi.com
3  -> Caddy on the Droplet
4  -> nginx container on 127.0.0.1:8346
5  -> Pretalx container

We bind the Docker Compose HTTP port to 127.0.0.1, so the container-level service is not directly exposed to the internet. Public traffic enters through Caddy on ports 80 and 443, and the DigitalOcean Cloud Firewall only needs to allow the expected public ports.

The Compose service is roughly shaped like this:

1services:
2  pretalx:
3    build:
4      context: .
5      dockerfile: Dockerfile
6      args:
7        PRETALX_IMAGE_TAG: ${PRETALX_IMAGE_TAG:-v2026.1.2}
8    image: asyncapi-pretalx:${PRETALX_IMAGE_TAG:-v2026.1.2}
9    volumes:
10      - ./conf/pretalx.cfg:/etc/pretalx/pretalx.cfg:ro
11      - pretalx-data:/data
12      - pretalx-public:/public
13
14  nginx:
15    image: nginx:1.27-alpine
16    ports:
17      - '127.0.0.1:${PRETALX_HTTP_PORT:-8346}:80'
18    volumes:
19      - ./nginx-container.conf:/etc/nginx/conf.d/default.conf:ro
20      - pretalx-public:/public:ro
21
22  db:
23    image: postgres:15-alpine
24
25  redis:
26    image: redis:7-alpine

Only the Pretalx image tag is configurable. The supporting service images are pinned, so upgrades are deliberate rather than accidental.

Why nginx and Caddy Both Exist

Caddy and nginx solve different problems.

Caddy is the public reverse proxy. It handles HTTPS, trusted certificates, and the public domain:

1cfp.asyncapi.com {
2	reverse_proxy 127.0.0.1:8346
3}

nginx is inside the Compose stack. It serves uploaded media and static files from the shared /public volume, then proxies all application routes to Pretalx:

1map $http_x_forwarded_proto $pretalx_forwarded_proto {
2    default $http_x_forwarded_proto;
3    "" $scheme;
4}
5
6server {
7    listen 80;
8    client_max_body_size 3m;
9
10    location /media/ {
11        alias /public/media/;
12    }
13
14    location /static/ {
15        alias /public/static/;
16    }
17
18    location / {
19        proxy_pass http://pretalx:80;
20        proxy_set_header Host $http_host;
21        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22        proxy_set_header X-Forwarded-Proto $pretalx_forwarded_proto;
23    }
24}

This split matters for image uploads. Pretalx can successfully upload and generate files, but if /media/... is not routed to the shared public volume, the browser will still see a 404 for speaker avatars or event images.

Pretalx Configuration

Pretalx reads its runtime configuration from a generated config file. The deployment workflow renders this file from a template and keeps secrets in GitHub environment secrets.

The key filesystem and upload settings are:

1[filesystem]
2data = /data
3logs = /data/logs
4media = /public/media
5static = /public/static
6
7[files]
8upload_limit = 2
9
10[site]
11url = https://cfp.asyncapi.com

The url value needs to be the public URL, not the internal container URL or a Droplet IP with a port. Pretalx uses this value when it builds absolute links. If it points to localhost or :8346, relative media URLs may work while absolute links break.

The upload limit is intentionally small. Speaker avatars and event images should be optimised before upload, and the Droplet should not spend storage or CPU resources on oversized media files.

Custom Pretalx Plugin

Pretalx provides us with the CFP and schedule data, but the conference website also needs location metadata specific to our site. For example:

  • city
  • country
  • venue address
  • map URL
  • event image URL

We added a small Pretalx plugin for that. Organisers can fill the fields in Pretalx, and the plugin exposes them through a public event endpoint:

GET /api/events/{event}/p/asyncapi-cfp/event-info/

The response is website-friendly:

1{
2  "city": "New York",
3  "country": "United States",
4  "address": "Conference venue address",
5  "map_url": "https://maps.example.com/...",
6  "image_url": "https://example.com/new-york.webp"
7}

The image_url field lets city lists and venue pages display location-specific images without hardcoding them directly in website components.

Conference Website Sync

The website does not fetch Pretalx on every page view. Instead, we sync public Pretalx data into the repository as generated JSON.

The workflow can be run manually or on a schedule:

1name: Sync Pretalx Schedule
2
3on:
4  workflow_dispatch:
5  schedule:
6    - cron: '0 7 * * 1'
7
8jobs:
9  sync:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v4
13      - uses: actions/setup-node@v4
14        with:
15          node-version: '20'
16          cache: npm
17      - run: npm ci
18      - run: npm run sync:pretalx
19        env:
20          PRETALX_SITE_URL: ${{ vars.PRETALX_SITE_URL }}
21          PRETALX_API_TOKEN: ${{ secrets.PRETALX_API_TOKEN }}

The sync writes three files:

1config/pretalx/city-lists.json
2config/pretalx/speakers.json
3config/pretalx/agenda.json

Those files are then merged into the existing conference website data model. This keeps the website static and reviewable: every sync opens a pull request, and maintainers can inspect what changed before it reaches production.

The agenda behaviour also follows the Pretalx schedule state. If the schedule is released, the website can display agenda data even if the CFP deadline has not yet passed. If no schedule has been released, the website can continue to show the CFP flow for that location.

Deployment Workflow

The deployment workflow is application deployment, not full infrastructure provisioning. The Droplet is prepared once, and GitHub Actions handles repeat deploys.

The deploy workflow does these steps:

  1. Render pretalx.cfg from the template.
  2. Upload the Compose file, nginx config, generated config, and plugin.
  3. Write the remote .env.
  4. Pull pinned base service images.
  5. Build only the custom Pretalx image.
  6. Start the Compose stack.
  7. Run a public health check.

The remote environment file stays small:

1POSTGRES_PASSWORD=...
2PRETALX_HTTP_PORT=8346
3PRETALX_IMAGE_TAG=v2026.1.2
4PRETALX_FILE_UPLOAD_LIMIT=2
5GUNICORN_FORWARDED_ALLOW_IPS=127.0.0.1

The one-time Droplet setup includes:

  • Create a pretalx deploy user
  • Install Docker and the Compose plugin
  • Create /opt/asyncapi-pretalx
  • Point DNS to the Droplet
  • Allow inbound 22, 80, and 443 in the DigitalOcean firewall
  • Install Caddy
  • Configure backups and periodic jobs

Email Delivery

Pretalx sends account, submission, proposal, and schedule emails. Local development uses Mailpit, so emails are captured in a browser instead of being sent externally.

Production uses Mailjet:

1[mail]
2from = cfp@asyncapi.com
3host = in-v3.mailjet.com
4port = 2525
5user = <mailjet-api-key>
6password = <mailjet-secret-key>
7tls = True
8ssl = False

The sender address and domain need to be verified in Mailjet. The domain also needs the SPF, DKIM, and DMARC records Mailjet provides. Without that DNS setup, email can be technically sent but still fail deliverability checks.

Backups and Operations

There are two critical data areas:

  • PostgreSQL database.
  • Pretalx data and public media volumes.

Redis is not backed up because Pretalx uses it for temporary and cached data.

The backup job dumps PostgreSQL and archives the Docker volumes:

1docker compose --env-file .env exec -T db \
2  pg_dump -U pretalx -d pretalx | gzip > "backups/postgres/pretalx-${stamp}.sql.gz"
3
4docker run --rm \
5  -v "${data_volume}:/data:ro" \
6  -v "/opt/asyncapi-pretalx/backups/volumes:/backup" \
7  alpine tar -czf "/backup/pretalx-data-${stamp}.tgz" -C /data .
8
9docker run --rm \
10  -v "${public_volume}:/public:ro" \
11  -v "/opt/asyncapi-pretalx/backups/volumes:/backup" \
12  alpine tar -czf "/backup/pretalx-public-${stamp}.tgz" -C /public .

The public volume is included because it contains uploaded speaker images, event images, generated thumbnails, and other media that users should not have to recreate manually.

We also run Pretalx periodic tasks from cron:

115,45 * * * * cd /opt/asyncapi-pretalx && docker compose --env-file .env exec -T pretalx pretalx runperiodic
20 8 * * * /opt/asyncapi-pretalx/bin/backup-pretalx.sh >> /opt/asyncapi-pretalx/backups/backup.log 2>&1

For production, backups should not live only on the Droplet. The local backup directory can be synced to DigitalOcean Spaces or another external object store.

Testing the Setup

The most useful checks were operational:

1curl -I https://cfp.asyncapi.com/healthcheck
2curl -I https://cfp.asyncapi.com/static/pretalx-manifest.json
3curl -I https://cfp.asyncapi.com/media/<uploaded-file>

For the plugin endpoint:

curl https://cfp.asyncapi.com/api/events/<event-slug>/p/asyncapi-cfp/event-info/

For the containers:

1docker compose --env-file .env ps
2docker compose --env-file .env logs --tail=100 pretalx
3docker compose --env-file .env logs --tail=100 nginx
4docker compose --env-file .env exec pretalx supervisorctl status

These checks caught issues that normal unit tests would not: incorrect public URLs, missing media routing, broken absolute image links, incorrect volume permissions, and health checks failing because redirects were not followed.

What DigitalOcean Gives Us

DigitalOcean gives us a practical operating model for this system. Droplet, Firewall, DNS, persistent Docker volumes, and optional Spaces backups are enough for this workload while remaining easy for maintainers to understand.

The main benefit is ownership. We can tune the stack, manage upgrades, maintain backups, expose the right public endpoints, and connect Pretalx to the conference website without relying on manual exports or third-party event workflows.

As the AsyncAPI Conference continues to grow across locations, this gives us a more sustainable foundation: Pretalx for conference operations, DigitalOcean for hosting, GitHub Actions for deployment and sync, and the AsyncAPI website for the public experience.

Lessons

The biggest lesson is that owning the process means owning the operational details too. Installing an Open Source tool is only the first part. Media routing, upload limits, email delivery, backups, TLS, health checks, and sync workflows decide whether the system is usable in practice.

The second lesson is that the website should not become the source of truth for conference operations. Pretalx should own the event workflow, and the website should consume clean, generated data.

Finally, small infrastructure can still be production-grade if the boundaries are clear. The setup works because the stack is explicit, documented, backed up, and integrated with the rest of our workflow via GitHub Actions.