Deploying the Website

The F-Droid website is built using Jekyll and gitlab-ci. The whole website now works using a standard git “fork” workflow that is well supported by gitlab, and well known from services like GitHub. For all of the pages and information about apps and packages distributed by, those pages are generated using our jekyll-fdroid plugin, which takes the content from the index file.

Staging on development forks

All development forks of fdroid-website automatically have a staging server setup and maintained by the gitlab-ci configuration. This automatically deploys the content of the fork’s master branch to GitLab Pages. For example, nicoalt’s git fork is at, and the master branch from that is automatically deployed to

Deploying merge request branches

The current gitlab-ci setup is optionally capable of pushing the resulting HTML to a live webserver and linking to it automatically from the GitLab merge request, deploying to

Doing so will allow for easier review of your merge requests, but is not required. If you don’t follow the steps below, your merge requests will still run through the gitlab-ci continuous integration checks, but the resulting HTML will not be accessible online.

To configure this:

  • Install the CLI: npm install surge
  • Signup for surge login
  • Get a “token” to allow CI to deploy on your behalf: surge token
  • Add two “Secret Variables” to your fork of this project:
    • Navigate to your projects Settings -> Pipelines -> Secret Variables.
    • Add the following two variables:
      • SURGE_LOGIN: The email you used to signup with surge login.
      • SURGE_TOKEN: The value given when you ran surge token.

Now your CI jobs should be authorized to deploy to, and the GitLab merge requests screen will automatically link to this deployment.

Staging of the official website

Like with forks, the master branch of the main git repo for the website,, is automatically deployed to That is the place to review the current state of the website before tagging a release.

Deploying to

When an update to the website is tested and ready to go, a release manager creates a PGP-signed release tag in the main git repo. The deploy server monitors the main git repo for new tags. When it sees a new tag, it first checks the PGP signature on the git tag using a manually configured GnuPG keyring that contains only the public keys of the PGP keys that are allowed to tag website releases.

After the git tag is verified, the target in .gitlab-ci.yml is run to generate the actual files for the site. Those files are then copied into place on the servers.

The deploy tags use a “semantic versioning” naming scheme:

  • <major>.<minor>
  • <minor> is incremented on each deployment
  • <major> is only incremented when there are major changes

Setting up and running the deploy procedure

The deploy procedure was tested on a machine running Debian/stretch. It should be triggered whenever the repo index is published, so it can rebuild with the latest app information. This whole procedure can be run as root, or just gitlab-runner. This final procedure is not part of a script committed into the website git repo so that the commands that run outside of docker cannot be modified via git.

  1. setup docker
  2. setup gitlab-ci-multi-runner
  3. prepare deploy-whitelist-keyring.gpg using GnuPG:
    $ gpg --recv-keys 00aa5556
    $ gpg --fingerprint 00aa5556  # verify it!
    $ gpg --export 00aa5556 > /path/to/deploy-whitelist-keyring.gpg
  4. get the website source code:
    $ git clone
  5. run the generation:
    $ cd fdroid-website
    $ git fetch --tags
    $ sudo gitlab-runner exec docker \
              --pre-build-script ./ \
              --docker-volumes "/path/to/deploy-whitelist-keyring.gpg:/root/.gnupg/pubring.gpg:ro" \
              --docker-volumes `pwd`/_site:/builds/output --env DEPLOY_DIR=/builds/output
  6. deploy the site’s files to the webserver, while preventing the jekyll generated files from overwriting parts of the website that other things manage:
    $ rsync -ax --delete --exclude-from=_site/build/.rsync-deploy-exclude-list  _site/build/

Apache2 config

The F-Droid website is translated into several languages. There are a few things required to ensure this works.

The first is taken care of by .gitlab-ci.yml, which is to run the ./tools/ script without the --no-type-maps argument. This ensures that each .html file is replaced with an Apache2 TypeMap.

The second is to enable mod_rewrite, which is used to automatically choose the correct language for browsers that are not using Javascript. The easiest way to do that is to run sudo a2enmod rewrite.

The last is to add the following to the Apache2 server or VirtualHost config so that the TypeMaps are used correctly, telling apache where to find the translated version of the file (replace /var/www/html with the actual webroot):

<Directory /var/www/html>
    AllowOverride All

<Files *.html>
    SetHandler type-map

# Virtualize the language sub"directories"
AliasMatch ^(?:/(?:ach|af|ak|sq|am|anp|ar|ar_DZ|ar_MA|an|es_AR|hy|as|ast|de_AT|ay|az|ba|eu|bar|be|be_Latn|bn|bn_BD|bn_IN|brx|bs|bs_Cyrl|bs_Latn|br|bg|my|ca|km|ch|chr|hne|cgg|zh|zh_HK|zh_Hans|zh_Hant|ksh|kw|cr|hr|cs|da|doi|nl|nl_BE|dz|en|en_AU|en_CA|en_IE|en_PH|en_ZA|en_GB|en_US|eo|et|fo|fil|fi|frp|fr|fr_CA|fy|fur|ff|gd|gl|ka|de|el|kl|gu|gun|ht|ha|haw|he|hi|hu|is|ig|id|ia|ga|it|ja|jv|kab|kn|ks|csb|kk|rw|tlh|tlh-qaak|kok|ko|ku|ckb|ky|lo|la|lv|li|ln|lt|jbo|nds|lb|mk|mai|mg|ms|ml|mt|mnk|mi|arn|mr|mni|mn|me|mfe|nqo|nah|nap|ne|se|no|nb_NO|nb|nn|ny|oc|or|oj|os|pap|nso|fa|pms|pr|pl|pt|pt_BR|pt_PT|pa|ps|ro|rm|ru|sa|sat|sc|sco|sr|sr_Cyrl|sr_Latn|sh|sn|szl|sd|si|sk|sl|so|son|st|es|es_US|es_MX|es_PR|su|sw|sv|de_CH|tl|tg|ta|tt|te|th|bo|ti|ts|tr|tk|ug|uk|hsb|ur|ur_PK|uz|uz_Latn|ca@valencia|ve|vec|vi|wa|cy|vls|wo|sah|yi|yo|yue|zu)/)?(.*)?$ /var/www/html/$1

# Tell mod_negotiation which language to prefer
SetEnvIf Request_URI ^/(ach|af|ak|sq|am|anp|ar|an|hy|as|ast|ay|az|ba|eu|bar|be|bn|brx|bs|br|bg|my|ca|km|ch|chr|hne|cgg|zh|ksh|kw|cr|hr|cs|da|doi|nl|dz|en|eo|et|fo|fil|fi|frp|fr|fy|fur|ff|gd|gl|ka|de|el|kl|gu|gun|ht|ha|haw|he|hi|hu|is|ig|id|ia|ga|it|ja|jv|kab|kn|ks|csb|kk|rw|tlh|tlh-qaak|kok|ko|ku|ckb|ky|lo|la|lv|li|ln|lt|jbo|nds|lb|mk|mai|mg|ms|ml|mt|mnk|mi|arn|mr|mni|mn|me|mfe|nqo|nah|nap|ne|se|no|nb|nn|ny|oc|or|oj|os|pap|nso|fa|pms|pr|pl|pt|pa|ps|ro|rm|ru|sa|sat|sc|sco|sr|sh|sn|szl|sd|si|sk|sl|so|son|st|es|su|sw|sv|tl|tg|ta|tt|te|th|bo|ti|ts|tr|tk|ug|uk|hsb|ur|uz|ca@valencia|ve|vec|vi|wa|cy|vls|wo|sah|yi|yo|yue|zu)/ prefer-language=$1

# Language codes from Weblate containing capital letters and underscores need to be treated
# differently, namely the language they refer to is lower case with a hyphen
SetEnvIf Request_URI ^/ar_DZ/ prefer-language=ar-dz
SetEnvIf Request_URI ^/ar_MA/ prefer-language=ar-ma
SetEnvIf Request_URI ^/be_Latn/ prefer-language=be-latn
SetEnvIf Request_URI ^/bn_BD/ prefer-language=bn-bd
SetEnvIf Request_URI ^/bn_IN/ prefer-language=bn-in
SetEnvIf Request_URI ^/bs_Cyrl/ prefer-language=bs-cyrl
SetEnvIf Request_URI ^/bs_Latn/ prefer-language=bs-latn
SetEnvIf Request_URI ^/de_AT/ prefer-language=de-at
SetEnvIf Request_URI ^/de_CH/ prefer-language=de-ch
SetEnvIf Request_URI ^/en_AU/ prefer-language=en-au
SetEnvIf Request_URI ^/en_CA/ prefer-language=en-ca
SetEnvIf Request_URI ^/en_GB/ prefer-language=en-gb
SetEnvIf Request_URI ^/en_IE/ prefer-language=en-ie
SetEnvIf Request_URI ^/en_PH/ prefer-language=en-ph
SetEnvIf Request_URI ^/en_US/ prefer-language=en-us
SetEnvIf Request_URI ^/en_ZA/ prefer-language=en-za
SetEnvIf Request_URI ^/es_AR/ prefer-language=es-ar
SetEnvIf Request_URI ^/es_MX/ prefer-language=es-mx
SetEnvIf Request_URI ^/es_PR/ prefer-language=es-pr
SetEnvIf Request_URI ^/es_US/ prefer-language=es-us
SetEnvIf Request_URI ^/fr_CA/ prefer-language=fr-ca
SetEnvIf Request_URI ^/nb_NO/ prefer-language=nb-no
SetEnvIf Request_URI ^/nl_BE/ prefer-language=nl-be
SetEnvIf Request_URI ^/pt_BR/ prefer-language=pt-br
SetEnvIf Request_URI ^/pt_PT/ prefer-language=pt-pt
SetEnvIf Request_URI ^/sr_Cyrl/ prefer-language=sr-cyrl
SetEnvIf Request_URI ^/sr_Latn/ prefer-language=sr-latn
SetEnvIf Request_URI ^/ur_PK/ prefer-language=ur-pk
SetEnvIf Request_URI ^/uz_Latn/ prefer-language=uz-latn
SetEnvIf Request_URI ^/zh_Hans/ prefer-language=zh-hans
SetEnvIf Request_URI ^/zh_Hant/ prefer-language=zh-hant
SetEnvIf Request_URI ^/zh_HK/ prefer-language=zh-hk

If this is not done or done incorrectly, then you will see something like the following when viewing any page:

URI: index.html.en Content-language: en Content-type: text/html URI: Content-language: fr Content-type: text/html

This is the result of the actual TypeMap being returned to the browser, rather than the translated file.

Note that this also depends on mod_alias and mod_negotiation being enabled, but this happens by default when installing apache2 on Debian.