Developer Tools Azure

Pontoon on Ubuntu 24.04 on Azure User Guide

| Product: Pontoon on Ubuntu 24.04 LTS on Azure

Overview

Pontoon is an open source localization platform: translation teams organised per locale, projects backed by your own version control repositories, a translate workbench with translation memory and machine translation suggestions, a review workflow, terminology management, and per team progress dashboards. It is the platform the Mozilla localization community builds and uses to localize Firefox and other projects.

The cloudimg image installs Pontoon from an exact pinned upstream commit into a Python 3.12 virtual environment, compiles the translate frontend at build time, and runs it as a systemd managed service behind nginx with PostgreSQL 16 and a Celery worker. Every secret the appliance uses is generated on your VM's first boot, and the whole stack is tuned to a stated memory ceiling so it fits a 4 GiB Standard_B2s without swap. Backed by 24/7 cloudimg support.

cloudimg is not affiliated with, sponsored by or endorsed by the Mozilla Foundation. Pontoon is distributed under the BSD-3-Clause licence; the upstream licence is kept on the image at /opt/pontoon/LICENSE.

What is included:

  • Pontoon pinned to upstream commit d493f14cafc81dfa53751291d49684c993c21fe1, served on port 80 by nginx in front of gunicorn
  • PostgreSQL 16 and RabbitMQ on loopback only, plus a Celery worker for project synchronisation
  • Pontoon's built in locale catalogue, so the Teams dashboard is populated the moment you sign in
  • A dedicated Azure data disk at /var/lib/pontoon holding the PostgreSQL cluster and the version control checkouts
  • A per VM administrator account and a per VM session signing key, generated on first boot and written to a root only file
  • postgresql@16-main, rabbitmq-server, pontoon, pontoon-celery and nginx as systemd units, enabled and active
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is the sized and tested configuration; see Step 10 for the arithmetic. NSG inbound: allow 22/tcp from your management network and 80/tcp for the Pontoon web interface. PostgreSQL, RabbitMQ and gunicorn are bound to loopback and are never exposed.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Pontoon by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Review the dedicated data disk on the Disks tab, then Review + create -> Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name pontoon \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name pontoon --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the Pontoon stack is running

On first boot the appliance generates a fresh session signing key, a fresh PostgreSQL password, a fresh RabbitMQ password and a fresh administrator password, writes them into place, and only then starts the application.

systemctl is-active postgresql@16-main rabbitmq-server pontoon pontoon-celery nginx

All five report active.

curl -s http://127.0.0.1/healthz

Returns ok. This is an unauthenticated static endpoint served by nginx, suitable for an Azure Load Balancer health probe.

Pontoon service status on Ubuntu 24.04

Only ports 22 and 80 leave the VM. PostgreSQL (5432), RabbitMQ (5672) and gunicorn (8000) are bound to 127.0.0.1.

ss -ltn | grep -E ':(5432|5672|8000) '

Step 5 - Confirm first boot completed

pontoon-firstboot.service is a one shot unit that runs once, on your very first boot, and stays reported as active afterwards because it declares RemainAfterExit=yes. On every later boot it is skipped by its own sentinel condition rather than disabling itself, so the unit file stays intact and you can always see what ran.

systemctl is-active pontoon-firstboot.service

Returns active.

test -f /var/lib/cloudimg/pontoon-firstboot.done && echo "first boot complete"

Step 6 - Retrieve your administrator credentials

The administrator account is generated on your VM and exists nowhere else. The credentials file is root only.

sudo cat /root/pontoon-credentials.txt

You will see your VM's URL, the sign in URL, the administrator username and its password. Keep this file or move the password into your own secret store.

sudo stat -c '%a %U:%G' /root/pontoon-credentials.txt

Returns 600 root:root.

The appliance also ships a check that proves the generated credential really authenticates. This matters more than it sounds: Pontoon's sign in view answers HTTP 200 when authentication fails, because it simply re renders the form with an error, so a status code on its own proves nothing. The check below performs a full browser shaped login and asserts a redirect, a session cookie, and access to a page only an administrator may see, then proves a deliberately wrong password does not achieve the same thing.

sudo /usr/local/sbin/pontoon-cred-roundtrip.sh

Pontoon authentication proof

Note wrong_password=200 in that output. That is exactly the trap described above, and why the check asserts on the redirect and the session rather than the status code.

Step 7 - Sign in

Browse to http://<vm-public-ip>/. The landing page is served without signing in.

Pontoon landing page

Choose Sign in, or go straight to http://<vm-public-ip>/accounts/standalone-login/, and enter the username and password from your credentials file.

Pontoon sign in page

This image is configured for local Django authentication (AUTHENTICATION_METHOD=django), which needs no external identity provider. Pontoon also supports GitHub, GitLab, Google, Keycloak and Mozilla Accounts sign in; see Step 13.

Once signed in, Teams lists the locale catalogue that ships with the image, with each locale's code and literate speaker count. Projects you add later report their progress against these teams.

Pontoon Teams dashboard

Step 8 - The translate workbench

Pontoon's translate workbench is where localizers actually work: the source string list on the left, the string being translated with its context in the centre, translation memory and machine translation suggestions on the right, and terminology and comments alongside.

The image ships Pontoon's own Tutorial project, so the workbench has real strings in it before you add anything of your own. Open a locale's tutorial, for example http://<vm-public-ip>/fr/tutorial/all-resources/.

Pontoon translate workbench

Type a translation and press Enter to save it. Placeables such as <b> and %(types)s are highlighted so they are not translated by mistake.

Step 9 - Add your first project

Pontoon stores translations in your own version control repositories. Adding a project is the first real task on a new instance.

Pontoon project administration

  1. Sign in as the administrator and open http://<vm-public-ip>/admin/.
  2. Choose ADD NEW PROJECT.
  3. Give the project a name and slug, add your repository URL under Repositories, and enable the locales you want to translate into.
  4. Save, then use the Sync control to pull the strings in.

Synchronisation runs on the Celery worker, not in the web request, so a large first import does not block the interface. You can also run it from the command line:

sudo -u pontoon bash -c 'set -a; . /etc/pontoon/pontoon.env; set +a; cd /opt/pontoon/app && /opt/pontoon/venv/bin/python manage.py sync_projects --projects=<your-project-slug>'

Pontoon expects synchronisation to run regularly. Add a timer or cron entry that runs sync_projects at least hourly once you have projects configured.

Step 10 - The memory budget

This is the heaviest stack cloudimg ships on Standard_B2s (2 vCPU / 4 GiB), so rather than leaving the components at their defaults every one of them is capped to a stated figure, and those caps are asserted at build time.

Component Allowance How it is capped
OS, systemd, sshd, waagent 400 MiB baseline
PostgreSQL 16 450 MiB shared_buffers 256MB, max_connections 40, work_mem 4MB
RabbitMQ 250 MiB vm_memory_high_watermark.relative 0.15
gunicorn 800 MiB 2 workers, recycled every ~400 requests
Celery 400 MiB concurrency 1, child recycled every 20 tasks
nginx 40 MiB default worker pool
Ceiling 2340 MiB of 3910 MiB, leaving about 1570 MiB headroom

You can see the live figures on your own VM:

free -m

Pontoon memory budget on Standard_B2s

sudo -u postgres psql -tAc "SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','work_mem','max_connections') ORDER BY name"

There is no swap on this image, by design: a swap file or swap partition baked into the OS disk is rejected by Azure image certification, and a stack that only fits with swap is a stack that needs a larger VM. The figures above are the whole story.

swapon --show; echo "swap devices: $(swapon --show --noheadings | wc -l)"

If you plan to run several large projects with frequent synchronisation, move up to a Standard_B2ms or Standard_D2s_v5 and raise --workers in /etc/systemd/system/pontoon.service and --concurrency in /etc/systemd/system/pontoon-celery.service to match the extra memory.

Step 11 - The dedicated data disk

The PostgreSQL cluster and the version control checkouts live on a dedicated Azure data disk, so they can be resized independently of the OS disk and are re-provisioned with every VM created from this image.

df -h /var/lib/pontoon | tail -1
sudo -u postgres psql -tAc 'SHOW data_directory'

Returns a path under /var/lib/pontoon. The application code, the virtual environment and the collected static assets stay on the OS disk under /opt/pontoon.

Pontoon version and shipped data

Step 12 - Version pinning

Pontoon has no usable release tags: it is deployed continuously from main and the newest tag predates 2019. This image is therefore pinned to an exact upstream commit rather than to a branch, so a rebuild can never drift.

cat /opt/pontoon/UPSTREAM_COMMIT

The bundled developer documentation is served at http://<vm-public-ip>/docs/.

Step 13 - Optional: use an external identity provider

To sign in with GitHub, GitLab, Google, Keycloak or Mozilla Accounts instead of local accounts, register an OAuth application with your provider and set the matching variables in /etc/pontoon/pontoon.env, for example:

AUTHENTICATION_METHOD=github
GITHUB_CLIENT_ID=<your-client-id>
GITHUB_SECRET_KEY=<your-client-secret>

Then restart the application:

sudo systemctl restart pontoon.service

Set SITE_URL and CSRF_TRUSTED_ORIGINS to the public URL your users reach, since OAuth redirect URLs must match exactly.

Step 14 - Put Pontoon behind HTTPS

The image serves plain HTTP on port 80 so it works the moment it boots. For anything beyond evaluation, terminate TLS in front of it. Point a DNS name at the VM, then either place Pontoon behind an Azure Application Gateway or Front Door with a managed certificate, or install a certificate on the VM with certbot --nginx.

When you move to HTTPS, update /etc/pontoon/pontoon.env:

SITE_URL=https://<your-domain>
CSRF_TRUSTED_ORIGINS=https://<your-domain>
SESSION_COOKIE_SECURE=True

and restart pontoon.service. Leave SECURE_SSL_REDIRECT=False if a proxy in front of you already redirects, or set it to True to have Pontoon do it.

Step 15 - Maintenance

Application logs:

sudo journalctl -u pontoon.service -n 20 --no-pager

Worker logs:

sudo journalctl -u pontoon-celery.service -n 20 --no-pager

Restart the application:

sudo systemctl restart pontoon.service pontoon-celery.service

Back up the database:

sudo -u postgres pg_dump -Fc pontoon > /var/lib/pontoon/pontoon-$(date +%F).dump

The OS applies security updates automatically through unattended-upgrades, which stays enabled on this image.

Troubleshooting

A POST is rejected with "CSRF verification failed". Pontoon compares the browser's Origin header against its own idea of the request origin. If you are reaching the VM through a proxy or an SSH tunnel on a non standard port, add that exact origin, including the port, to CSRF_TRUSTED_ORIGINS in /etc/pontoon/pontoon.env and restart pontoon.service.

The application does not start after a restore or a manual configuration change. pontoon.service and pontoon-celery.service deliberately refuse to start unless /var/lib/cloudimg/pontoon-bootstrap-ready exists. That marker is created by first boot after the per VM secrets are written, and it is what stops the application coming up against placeholder credentials. If it is missing, check journalctl -u pontoon-firstboot.service.

Sign in returns to the same form. That is an authentication failure, not an error page. Re read the password from /root/pontoon-credentials.txt, and confirm with sudo /usr/local/sbin/pontoon-cred-roundtrip.sh.

Support

cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk.