Skip to Content

Production Guide: Deploy Weblate with Docker Compose + nginx + PostgreSQL + Redis on Ubuntu

Self-hosted continuous localization with secure secrets, database backups, health checks, and production hardening.

Engineering teams building products for global markets face a hidden bottleneck: translations. When product copy, error messages, and documentation are scattered across spreadsheets, emailed to agencies, and pasted back by hand, consistency breaks and releases slip. Weblate is an open-source web-based continuous localization platform that brings translators, reviewers, and developers into the same workflow. It integrates directly with Git repositories, tracks translation progress per language, and raises pull requests back to your codebase so localized strings ship with every release.

In this guide, we will deploy Weblate on a single Ubuntu host with Docker Compose, publish it through nginx with automatic HTTPS, connect it to PostgreSQL for data durability, add Redis for caching and locking, and configure the operational controls that make it safe for production: secret management, database backups, health checks, and a clear acceptance checklist.

Architecture and flow overview

The deployment uses four containers on a dedicated bridge network: Weblate, PostgreSQL, Redis, and nginx. Weblate serves the Django application and Celery workers on an internal port. PostgreSQL persists translations, projects, user accounts, and version control metadata. Redis handles caching, session storage, and Celery task queues. nginx terminates TLS, handles certificate renewal, and proxies requests to Weblate. Persistent data lives in named Docker volumes for the database and Redis, and in a bind mount for Weblate data and static files. A nightly cron job dumps the PostgreSQL database to an encrypted archive on the host.

When a translator opens Weblate in a browser, the request flows through nginx on port 443, is decrypted, and forwarded to the Weblate container on the internal Docker network. Weblate queries PostgreSQL for the requested project or translation, renders the editor, and returns the HTML. When a developer pushes new source strings to a connected Git repository, Weblate detects the change, updates the translation status, and optionally opens a pull request with completed translations. nginx manages ACME challenges and automatically renews the TLS certificate before expiry.

  • nginx handles public HTTPS on port 443 and auto-redirects HTTP to HTTPS.
  • Weblate runs the Django application server with the built-in web UI, REST API, and Celery background workers.
  • PostgreSQL stores projects, components, translations, user accounts, and version control metadata.
  • Redis provides caching, session storage, and Celery task broker functionality.
  • Docker volumes persist the database files, Redis data, and Weblate application data across restarts.
  • Host cron runs pg_dump nightly and rotates encrypted backups locally or to S3.

Prerequisites

  • Ubuntu 22.04 or 24.04 LTS with SSH access and sudo privileges.
  • A DNS A record pointing weblate.yourdomain.com to the server public IP.
  • Docker Engine 24.x+ and Docker Compose plugin installed.
  • Ports 80 and 443 open to nginx for ACME challenges and HTTPS traffic.
  • At least 2 GB RAM and 20 GB disk for repositories, translation files, and backups.
  • An SMTP provider account or local relay for password-reset and notification emails.

Step-by-step deployment

1) Install Docker, Compose, nginx, and firewall basics

Update the package index, install Docker and the Compose plugin, and enable the firewall with only the ports nginx needs:

sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER

sudo apt install -y nginx
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

2) Create directories and environment file

Create a dedicated directory for Weblate, generate strong passwords, and prepare the environment file:

sudo mkdir -p /opt/weblate/{data,backups}
sudo chown -R $USER:$USER /opt/weblate
cd /opt/weblate

DB_PASS=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
ADMIN_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
REDIS_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
cat > .env <

3) Define Compose services

The Compose file defines four services on a shared bridge network. Weblate loads environment variables from the external .env file and mounts the local data directory for persistent files. PostgreSQL uses a named volume for durability, and Redis uses a named volume with authentication:

cat > compose.yml <<'COMPOSE'
services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: weblate
      POSTGRES_USER: weblate
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U weblate -d weblate"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - weblatenet

  cache:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass "${REDIS_PASSWORD}"
    volumes:
      - redis_data:/data
    networks:
      - weblatenet

  weblate:
    image: weblate/weblate:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    env_file:
      - .env
    environment:
      WEBLATE_SITE_DOMAIN: weblate.yourdomain.com
    volumes:
      - ./data:/app/data
      - weblate_cache:/app/cache
    networks:
      - weblatenet

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot-data:/etc/letsencrypt
      - ./certbot-www:/var/www/certbot
    networks:
      - weblatenet
    depends_on:
      - weblate

volumes:
  db_data:
  redis_data:
  weblate_cache:

networks:
  weblatenet:
    driver: bridge
COMPOSE

4) Configure nginx reverse proxy

nginx handles TLS termination and proxies requests to the Weblate container. Create the nginx configuration with the reverse proxy block, security headers, and Certbot webroot support:

cat > nginx.conf <<'NGINX'
server {
    listen 80;
    server_name weblate.yourdomain.com;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name weblate.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/weblate.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/weblate.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;

    location / {
        proxy_pass http://weblate:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
NGINX

5) Obtain TLS certificate with Certbot

Run Certbot in standalone mode to obtain the initial certificate before starting the full stack:

sudo apt install -y certbot
sudo certbot certonly --standalone -d weblate.yourdomain.com --agree-tos -m [email protected] --non-interactive
sudo mkdir -p /opt/weblate/certbot-data/live/weblate.yourdomain.com
sudo cp /etc/letsencrypt/live/weblate.yourdomain.com/fullchain.pem /opt/weblate/certbot-data/live/weblate.yourdomain.com/
sudo cp /etc/letsencrypt/live/weblate.yourdomain.com/privkey.pem /opt/weblate/certbot-data/live/weblate.yourdomain.com/
sudo chown -R $USER:$USER /opt/weblate/certbot-data

6) Start services and verify health

Pull the images, start the stack, and confirm that PostgreSQL and Weblate reach a healthy state:

docker compose pull
docker compose up -d
sleep 30
docker compose ps
docker compose logs --tail 50 weblate

7) First-time admin setup

Open https://weblate.yourdomain.com in a browser. Log in with the admin credentials from your .env file. After login, create your first project, connect a Git repository, and define a component for translation. Verify that source strings are imported and that the translation editor loads without errors.

8) Backup script

Create a nightly backup script that dumps the PostgreSQL database and archives the Weblate data directory. Store the script in /opt/weblate/backups/backup.sh:

cat > /opt/weblate/backups/backup.sh <<'BKP'
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/opt/weblate/backups"
DATE=$(date +%Y%m%d_%H%M%S)
source /opt/weblate/.env

# Database dump
docker compose -f /opt/weblate/compose.yml exec -T db pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" | gzip > "$BACKUP_DIR/weblate_db_$DATE.sql.gz"

# Data directory archive
tar czf "$BACKUP_DIR/weblate_data_$DATE.tar.gz" -C /opt/weblate data

# Rotate to keep last 14 days
find "$BACKUP_DIR" -name 'weblate_*' -mtime +14 -delete
BKP
chmod +x /opt/weblate/backups/backup.sh

# Schedule nightly at 02:00
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/weblate/backups/backup.sh >> /opt/weblate/backups/backup.log 2>&1") | crontab -

9) Acceptance checklist execution

Run through the checklist to confirm the deployment is production-ready:

  • DNS resolves weblate.yourdomain.com to the server public IP.
  • curl -I https://weblate.yourdomain.com returns HTTP 200 with HSTS headers.
  • Login succeeds with the admin credentials defined in .env.
  • Create a test project, connect a Git repository, and import source strings successfully.
  • Verify that translation suggestions and comments are saved without errors.
  • A backup archive exists in /opt/weblate/backups/ after the first scheduled run.

Configuration and secrets handling

Never commit the .env file to version control. Store it in a secrets manager or encrypted backup. The admin password grants full access to all projects and translations; rotate it quarterly and require MFA for administrative accounts. For database credentials, create a dedicated PostgreSQL user with limited privileges rather than using the superuser account.

Backup archives should be encrypted at rest if they are copied to object storage. Consider restricting Weblate admin access to a VPN or IP allowlist. Translation data may contain proprietary product strings; review your data retention policy and scrub sensitive content before sharing exports with external teams. If you connect Weblate to private Git repositories, use deploy keys or dedicated service accounts rather than personal access tokens.

Verification

  • Run curl -I https://weblate.yourdomain.com and confirm HTTP 200 with strict-transport-security header.
  • Log in with the admin credentials and verify the dashboard loads.
  • Create a project, connect a Git repository, and confirm source strings import correctly.
  • Inspect the translation editor and confirm edits are saved and reflected in the database.
  • Check /opt/weblate/backups/ for a recent .sql.gz archive.
  • Verify that nginx has a valid certificate with docker compose logs nginx.

Common issues and fixes

Weblate shows a database connection error on first start: Ensure PostgreSQL has finished initializing before Weblate starts. The depends_on condition service_healthy handles this, but if you see repeated connection failures, run docker compose restart weblate after confirming docker compose logs db shows database system is ready.

Git repository push fails with authentication error: Verify that the deploy key or personal access token configured in the Weblate project settings has write access to the target repository. For GitHub, ensure the token has repo scope and that two-factor authentication is not blocking basic auth.

nginx returns 502 Bad Gateway: Verify that the nginx.conf proxy target matches the Compose service name weblate on port 8080. Check docker compose logs weblate to confirm the application is listening.

Out-of-memory errors during large repository imports: Weblate imports and parses translation files in memory. Increase the container memory limit or host RAM, and consider splitting large monolithic translation files into smaller components.

Certificate renewal fails behind a restrictive firewall: Certbot needs outbound HTTPS to the Let's Encrypt ACME servers. Whitelist acme-v02.api.letsencrypt.org and the relevant OCSP responders.

Email notifications fail to send: Confirm that WEBLATE_EMAIL_HOST, WEBLATE_EMAIL_HOST_USER, and WEBLATE_EMAIL_HOST_PASSWORD are correct in the .env file. Test SMTP connectivity with openssl s_client -connect smtp.yourdomain.com:587 -starttls smtp.

FAQ

Can I use MySQL or MariaDB instead of PostgreSQL?

Weblate officially supports PostgreSQL and MySQL. To use MariaDB, change the database service image, update the environment variables, and adjust the healthcheck command. PostgreSQL is recommended for large translation projects and long-term stability.

How do I add a new translation language to a project?

In the Weblate project settings, open the component and click Add new translation. Select the target language from the dropdown or enter a custom language code. Weblate will create the translation file in the repository and track progress automatically.

Does Weblate support SAML and SSO authentication?

Yes. Weblate supports OAuth2, OpenID Connect, SAML, LDAP, and Azure AD authentication out of the box. Configure the social authentication providers in the .env file or through the Django admin panel. Enterprise plans include advanced group synchronization and SCIM provisioning.

How do I configure automatic pull requests to GitHub?

In the component settings, enable the Version control integration and set the push URL to your GitHub repository. Add a deploy key or personal access token with repo scope. Enable Push on commit to automatically open pull requests when translations reach 100 percent completion.

What is the difference between Weblate Community and Enterprise?

Weblate Community is free, open-source, and includes unlimited projects, components, and languages. Weblate Enterprise adds advanced user management, priority support, hosted backups, and SLA guarantees. This guide uses the Community edition.

Can I run Weblate without Redis?

Weblate can use the Django database cache as a fallback, but Redis is strongly recommended for production. Redis handles Celery task brokering, session storage, and rate limiting. Without Redis, large imports and background tasks will run synchronously and may timeout.

How do I update Weblate?

Pull the latest image, recreate the container, and run database migrations automatically on startup. Because the data directory and database are persistent, the update is non-destructive. Always take a backup before major upgrades.

docker compose pull weblate
docker compose up -d weblate

Internal links

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Header image: Original SysBrix generated header, no watermark.

How to Deploy Grafana in Production with Docker Compose + systemd
A practical, production-focused implementation guide with operational checks and recovery playbooks.