Pontoon
Overview
Pontoon is an open source localization platform. It provides translation teams organised per locale, projects backed by your own version control repositories, and a translate workbench with translation memory and machine translation suggestions. It also provides a review workflow, terminology management and per-team progress dashboards. The Mozilla localization community builds Pontoon and uses it to localize Firefox and other projects.
The SecureAnyCloud image installs Pontoon from an exact pinned upstream commit into a Python 3.12 virtual environment and compiles the translate frontend at build time. It runs Pontoon 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. The whole stack is tuned to a stated memory ceiling so it fits a 4 GiB Standard_B2s without swap. The image is backed by 24/7 SecureAnyCloud support.
SecureAnyCloud 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 you get:
- 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/pontoonthat holds 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-celeryandnginxas systemd units, enabled and active.- 24/7 SecureAnyCloud 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. Step 10 shows the memory arithmetic.
- NSG inbound rules that allow
22/tcpfrom your management network and80/tcpfor 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 SecureAnyCloud, and select Create. On the Basics tab, 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 choose Review + create -> Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:pontoon-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name pontoon \
--image sac1647283583153:pontoon-ubuntu-24-04:default:latest \
--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 1010Step 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. It writes them into place and only then starts the application. Check that all five services are running:
systemctl is-active postgresql@16-main rabbitmq-server pontoon pontoon-celery nginxAll five report active.
curl -s http://127.0.0.1/healthzThe command returns ok. This unauthenticated static endpoint is served by nginx and is suitable for an Azure Load Balancer health probe.
Only ports 22 and 80 are reachable from outside the VM. PostgreSQL (5432), RabbitMQ (5672) and gunicorn (8000) are bound to 127.0.0.1. Confirm this with:
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. It stays reported as active afterwards because it declares RemainAfterExit=yes. On every later boot its own sentinel condition skips it instead of the unit disabling itself, so the unit file stays intact and you can always see what ran.
systemctl is-active pontoon-firstboot.serviceThe command returns active. Then confirm the sentinel file exists:
test -f /var/lib/sac/data/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 readable by root only.
sudo cat /root/pontoon-credentials.txtThe file shows your VM's URL, the sign-in URL, the administrator username and its password. Keep the file, or move the password into your own secret store. Check the file's permissions:
sudo stat -c '%a %U:%G' /root/pontoon-credentials.txtThe command returns 600 root:root.
The appliance also includes a check that proves the generated credential really authenticates. A status code alone cannot prove this, because Pontoon's sign-in view answers HTTP 200 when authentication fails. It simply re-renders the form with an error. The check performs a full browser-style login and asserts a redirect, a session cookie, and access to a page that only an administrator may see. It then proves that a deliberately wrong password does not achieve the same result.
sudo /usr/local/sbin/pontoon-cred-roundtrip.shThe output includes wrong_password=200. This is the behavior described above, and it is 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.
Choose Sign in, or go straight to http://<vm-public-ip>/accounts/standalone-login/. Enter the username and password from your credentials file.
This image uses local Django authentication (AUTHENTICATION_METHOD=django), which needs no external identity provider. Pontoon also supports sign-in with GitHub, GitLab, Google, Keycloak and Mozilla Accounts. See Step 13.
Once you are 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.
Step 8 - The translate workbench
The translate workbench is where localizers do their work. It shows the source string list on the left, the string being translated with its context in the centre, and translation memory and machine translation suggestions on the right. Terminology and comments appear alongside.
The image includes 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/.
Type a translation and press Enter to save it. Pontoon highlights placeables such as <b> and %(types)s so that you do not translate them by mistake.
Step 9 - Add your first project
Pontoon stores translations in your own version control repositories, so adding a project is the first real task on a new instance.
- Sign in as the administrator and open
http://<vm-public-ip>/admin/. - Choose ADD NEW PROJECT.
- Give the project a name and slug, add your repository URL under Repositories, and enable the locales you want to translate into.
- Save the project, 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. Once you have projects configured, add a timer or cron entry that runs sync_projects at least hourly.
Step 10 - The memory budget
This is the heaviest stack SecureAnyCloud ships on Standard_B2s (2 vCPU / 4 GiB). Instead of leaving the components at their defaults, the image caps every one of them to a stated figure and asserts those caps at build time. The table rows below list each component, its allowance and how it is capped.
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
To see the live figures on your own VM, run:
free -msudo -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. Azure image certification rejects a swap file or swap partition baked into the OS disk, and a stack that only fits with swap needs a larger VM. The figures above are the whole memory budget. Confirm that no swap is configured:
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. Then 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. You can resize it independently of the OS disk, and it is re-provisioned with every VM created from this image. Check the mount and the PostgreSQL data directory:
df -h /var/lib/pontoon | tail -1sudo -u postgres psql -tAc 'SHOW data_directory'The last command 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.
Step 12 - Version pinning
Pontoon has no usable release tags. Upstream deploys it 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 cannot drift. To see the pinned commit, run:
cat /opt/pontoon/UPSTREAM_COMMITThe 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. Then set the matching variables in /etc/pontoon/pontoon.env. For example, for GitHub:
AUTHENTICATION_METHOD=github GITHUB_CLIENT_ID=<your-client-id> GITHUB_SECRET_KEY=<your-client-secret>Then restart the application:
sudo systemctl restart pontoon.serviceSet SITE_URL and CSRF_TRUSTED_ORIGINS to the public URL your users reach, because OAuth redirect URLs must match exactly.
Step 14 - Put Pontoon behind HTTPS
The image serves plain HTTP on port 80 so that 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=TrueThen restart pontoon.service. Leave SECURE_SSL_REDIRECT=False if a proxy in front of Pontoon already redirects to HTTPS. Otherwise set it to True to have Pontoon do the redirect.
Step 15 - Maintenance
Application logs:
sudo journalctl -u pontoon.service -n 20 --no-pagerWorker logs:
sudo journalctl -u pontoon-celery.service -n 20 --no-pagerRestart the application:
sudo systemctl restart pontoon.service pontoon-celery.serviceBack up the database:
sudo -u postgres pg_dump -Fc pontoon > /var/lib/pontoon/pontoon-$(date +%F).dumpThe 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 view of the request origin. If you reach 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. Then 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/sac/data/pontoon-bootstrap-ready exists. First boot creates that marker after it writes the per-VM secrets, and the marker stops the application from starting before the per-VM secrets are in place. If the marker is missing, check journalctl -u pontoon-firstboot.service.
Sign-in returns to the same form. This is an authentication failure, not an error page. Read the password again from /root/pontoon-credentials.txt, and confirm it with sudo /usr/local/sbin/pontoon-cred-roundtrip.sh.
Support
SecureAnyCloud provides 24/7 support for this image. Contact sales@secureanycloud.com.
Deploy on Azure
Launch Pontoon on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com