eQ Questionnaire Runner
September 18, 2026 ยท View on GitHub
Run with Docker
Install Docker for your system. Make sure that you've installed both docker and docker-compose packages, preferably using Homebrew:
brew install docker
brew install docker-compose
On MacOS install container runtimes, eg. Colima:
brew install colima
Make sure Colima is started every time you want to use Docker images:
colima start
To get eq-questionnaire-runner running the following command will build and run the containers
RUNNER_ENV_FILE=.development.env docker compose up -d
To launch a survey, navigate to http://localhost:8000/
When the containers are running you are able to access the application as normal, and code changes will be reflected in the running application. However, any new dependencies that are added would require a re-build.
To rebuild the eq-questionnaire-runner container, the following command can be used.
RUNNER_ENV_FILE=.development.env docker compose build
If you need to rebuild the container from scratch to re-load any dependencies then you can run the following
RUNNER_ENV_FILE=.development.env docker compose build --no-cache
Debugging
As we use a distroless container for runner there is no shell or basic command line utilities for debugging the running container. If you need to enable this you can build a debug version of the image using the RUNTIME_BASE_IMAGE_TAG variable:
RUNNER_ENV_FILE=.development.env RUNTIME_BASE_IMAGE_TAG=debug docker compose up -d --build
Then you can shell into the running container with:
docker exec -it <container_id> sh
Run locally
Clone the repository
git clone git@github.com:ONSdigital/census31-eq-questionnaire-runner.git
Pre-Requisites
In order to run locally you'll need Node.js, snappy, pyenv and jq installed
brew install snappy npm pyenv jq
Setup
Application version
Create .application-version for local development
This file is automatically created and populated with the git revision id during CI for anything other than development,
but the file is absent when the repo is first cloned and is required for running the app locally. Setting the contents
to local removes the implication that any particular revision is used when run locally.
echo "local" > .application-version
Python version
It is preferable to use the version of Python locally that matches that
used on deployment. This project has a .python_version file for this
purpose.
Pyenv
It is recommended to install the pyenv Python version management tool to easily switch between Python versions.
To install pyenv use this command:
curl https://pyenv.run | bash
After the installation it should tell you to execute a command to add pyenv to path. It should look something like this:
export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
Python versions can be changed with the pyenv local or pyenv global commands suffixed with the desired version (e.g. 3.13.5). Different versions of Python can be installed first with the pyenv install command. Refer to the pyenv project README. To avoid confusion, check the current Python version at any given time using python --version or python3 --version.
Python & dependencies
Inside the project directory install python version, upgrade pip:
pyenv install
pip install --upgrade pip setuptools
Install poetry, poetry dotenv plugin and install dependencies:
curl -sSL https://install.python-poetry.org | python3 - --version 2.1.2
poetry self add poetry-plugin-dotenv
poetry install
We use poetry-plugin-up to update dependencies in the pyproject.toml file:
poetry self add poetry-plugin-up
Design system templates
To update the design system templates run:
make load-design-system-templates
Schemas
To download the latest schemas from the Questionnaire Registry:
make load-schemas
Run server
Run the server inside the virtual env created by Poetry with:
make run
Supporting services
Runner requires three supporting services - a questionnaire launcher, a storage backend, and a cache.
Run supporting services with Docker
First, authenticate to make sure Docker can pull from GAR
gcloud auth login
To run the app locally, but the supporting services in Docker, make sure you have Docker and Colima installed from this step, then run:
make dev-compose-up
If you also want to run the address index service (for address lookups), use:
make aims-compose-up
Using Google Cloud Platform for supporting services
To use Google Datastore and Google Cloud Storage (GCS) for submission and feedback backends directly on GCP and not a docker image, you need to set the GCP project using the following command:
gcloud config set project <gcp_project_id>
Or set the GOOGLE_CLOUD_PROJECT environment variable to your gcp project id.
Integration Tests
There is a dev-convenience script that auto generates the lines of code for a user journey. See README for more information and how to run the script.
Frontend Tests
The frontend tests use NodeJS to run. To handle different versions of NodeJS it is recommended to install Node Version Manager (nvm). It is similar to pyenv but for Node versions.
To install nvm use the command below (make sure to replace "v0.40.6" with the current latest version in releases):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
You will need to have the correct node version installed to run the tests:
nvm install
nvm use
Install npm dependencies and playwright browsers:
npm install
npx playwright install --with-deps
Runner needs to be run with the functional test environment variables:
RUNNER_ENV_FILE=.functional-tests.env make run
The functional tests use page models generated for each of the test schemas, generate them with:
make generate-pages
Then you can run either run the tests with:
make test-functional
or headless with:
make test-functional-headless
Both commands delete the tests/functional/generated_pages directory and regenerates all page models from the schemas.
Run a specific spec with (you only need the spec filename, not the path):
make test-functional-spec SPEC=<spec filename>
Run against a remote environment with:
EQ_FUNCTIONAL_TEST_ENV=https://staging-new-surveys.dev.eq.ons.digital/ make test-functional
More detailed information on running and debugging can be found in functional-tests.md
Deploying
For deploying with Concourse see the CI README.
Deployment with gcloud
To deploy this application with gcloud, you must be logged in using gcloud auth login and gcloud auth application-default login.
When deploying with gcloud the environment variables specified in Deploying the app must be set.
Then call the following command with environment variables set:
./ci/deploy_app.sh
Deploying credentials
Before deploying the app to GCP you need to create the application credentials. Run the following command to provision the credentials:
PROJECT_ID=PROJECT_ID EQ_KEYS_FILE=PATH_TO_KEYS_FILE EQ_SECRETS_FILE=PATH_TO_SECRETS_FILE ./ci/deploy_credentials.sh
For example:
PROJECT_ID=eq-test EQ_KEYS_FILE=dev-keys.yml EQ_SECRETS_FILE=dev-secrets.yml ./ci/deploy_credentials.sh
Deploying the app
The following environment variables must be set when deploying the app.
| Variable Name | Description |
|---|---|
| PROJECT_ID | The ID of the GCP target project |
| DOCKER_REGISTRY | The FQDN of the target Docker registry |
| IMAGE_TAG |
The following environment variables are optional:
| Variable Name | Default | Description |
|---|---|---|
| REGION | europe-west2 | The region that will be used for your Cloud Run service |
| CONCURRENCY | 80 | The maximum number of requests that can be processed simultaneously by a given container instance |
| MIN_INSTANCES | 1 | The minimum number of container instances that can be used for your Cloud Run service |
| MAX_INSTANCES | 1 | The maximum number of container instances that can be used for your Cloud Run service |
| CPU | 4 | The number of CPUs to allocate for each Cloud Run container instance |
| MEMORY | 4G | The amount of memory to allocate for each Cloud Run container instance |
| GOOGLE_TAG_ID | The Google Tag ID - Specifies the GTM account | |
| WEB_SERVER_TYPE | gunicorn-threads | Web server type used to run the application. This also determines the worker class which can be async/threaded |
| WEB_SERVER_WORKERS | 7 | The number of worker processes |
| WEB_SERVER_THREADS | 10 | The number of worker threads per worker |
| DATASTORE_USE_GRPC | False | Determines whether to use gRPC for Datastore. gRPC is currently only supported for threaded web servers |
To deploy the app, run the following command:
./ci/deploy_app.sh
Internationalisation
We use flask-babel to do internationalisation. To extract messages from source and create the messages.pot file, in the project root run the following command.
make translation-templates
make translation-templates is a command that uses pybabel to extract static messages.
This will extract messages and place them in the .pot files ready for translation.
These .pot files will then need to be translated. The translation process is documented in Confluence in the Translation Process guide
Once we have the translated .po files they can be added to the source code and used by the application
Environment Variables
The following env variables can be used
| Variable Name | Default | Description |
|---|---|---|
| EQ_SESSION_TIMEOUT_SECONDS | 2700 (45 mins) | The duration of the flask session |
| EQ_PROFILING | False | Enables or disables profiling (True/False) Default False/Disabled |
| EQ_GOOGLE_TAG_ID | The Google Tag Manager ID - Specifies the GTM account | |
| EQ_ENABLE_HTML_MINIFY | True | Enable minification of html |
| EQ_ENABLE_SECURE_SESSION_COOKIE | True | Set secure session cookies |
| EQ_MAX_HTTP_POST_CONTENT_LENGTH | 65536 | The maximum http post content length that the system will accept |
| EQ_MINIMIZE_ASSETS | True | Should JS and CSS be minimized |
| MAX_CONTENT_LENGTH | 65536 | Max request payload size in bytes |
| EQ_APPLICATION_VERSION_PATH | .application-version | The location of a file containing the application version number |
| EQ_SECRETS_FILE | secrets.yml | The location of the secrets file |
| EQ_KEYS_FILE | keys.yml | The location of the keys file |
| EQ_SUBMISSION_BACKEND | Which submission backend to use (gcs, log) | |
| EQ_GCS_SUBMISSION_BUCKET_ID | The bucket name in GCP to store the submissions in | |
| EQ_GCS_FEEDBACK_BUCKET_ID | The bucket name in GCP to store the feedback in | |
| EQ_SERVER_SIDE_STORAGE_USER_ID_ITERATIONS | 10000 | |
| EQ_QUESTIONNAIRE_STATE_TABLE_NAME | ||
| EQ_SESSION_TABLE_NAME | ||
| EQ_USED_JTI_CLAIM_TABLE_NAME | ||
| EQ_REDIS_HOST | Hostname of Redis instance used for ephemeral storage | |
| EQ_REDIS_PORT | Port number of Redis instance used for ephemeral storage | |
| WEB_SERVER_TYPE | Web server type used to run the application. This also determines the worker class which can be async/threaded | |
| WEB_SERVER_WORKERS | The number of worker processes | |
| WEB_SERVER_THREADS | The number of worker threads per worker | |
| DATASTORE_USE_GRPC | False | Determines whether to use gRPC for Datastore. gRPC is currently only supported for threaded web servers |
| ACCOUNT_SERVICE_BASE_URL | https://start.census.gov.uk | The base URL of the account service used to launch the survey |
| ONS_URL | https://www.ons.gov.uk | The URL of the ONS website where static content is sourced, e.g. accessibility info |
The following env variables can be used when running tests
EQ_FUNCTIONAL_TEST_ENV - the pre-configured environment [local, docker, preprod] or the url of the environment that should be targeted
JWT Integration
Integration with the survey runner requires the use of a signed JWT using public and private key pair (see https://jwt.io, https://tools.ietf.org/html/rfc7519, https://tools.ietf.org/html/rfc7515).
Once signed the JWT must be encrypted using JWE (see https://tools.ietf.org/html/rfc7516).
The JWT payload must contain the following claims:
- exp - expiration time
- iat - issued at time
The header of the JWT must include the following:
- alg - the signing algorithm (must be RS256)
- type - the token type (must be JWT)
- kid - key identification (must be EDCRRM)
The JOSE header of the final JWE must include:
- alg - the key encryption algorithm (must be RSA-OAEP)
- enc - the key encryption encoding (must be A256GCM)
To access the application you must provide a valid JWT. To do this browse to the /session url and append a token parameter. This parameter must be set to a valid JWE encrypted JWT token. Only encrypted tokens are allowed.
There is a python script for generating tokens for use in development, to run:
python token_generator.py
Profiling
Refer to our profiling document.
Updating / Installing dependencies
Python
To add a new dependency, use:
poetry add [package-name]
This will add the required packages to your pyproject.toml and install them
To update a dependency, use:
poetry update [package-name]
This will resolve the required dependencies of the project and write the exact versions into poetry.lock
Using the poetry up plugin we can update dependencies and bump their versions in the pyproject.toml file
To update dependencies to the latest compatible version with respect to their version constraints specified in the pyproject.toml file:
poetry up
To update dependencies to their latest compatible version:
poetry up --latest
NB: both the pyproject.toml and poetry.lock files are required in source control to accurately pin dependencies.
JavaScript
To add a new dependency, use npm install [dev dependency] --save-dev or npm install [dependency] then use npm install to install all the packages locally.
Testing Design System changes (locally) without pushing to actual CDN
On Design System Repo
Checkout branch with new changes on
You will need to install the Design System dependencies. If you haven't installed Yarn, install it with npm i -g yarn. To install the dependencies run yarn in the terminal. If you haven't
you will also need to install gulp.
Then in the terminal run:
yarn cdn-bundle
cd build
browser-sync start --cwd -s --http --port 5678
You should now see output indicating that files are being served from localhost:5678. So main.css for example will now be served on http://localhost:5678//css/main.css
Now switch to the eQ Questionnaire Runner Repo
On eQ Questionnaire Runner Repo
In a separate terminal window/tab: Checkout the runner branch you want to test on
Edit your .development.env with following:
CDN_URL=http://localhost:5678
CDN_ASSETS_PATH=
Edit the Makefile to remove load-design-system-templates from the build command. Should now look like this:
build: load-schemas translate
Run make load-design-system-templates in the terminal to make sure you have the Design System templates loaded
Then edit the first line in the templates/layout/_template.njk file to remove the version number. Should now look like this:
{% set release_version = '' %}
Then spin up launcher and runner with make dev-compose-up and make run
Now when navigating to localhost:8000 and launching a schema, this will now be using the local cdn with the changes from the Design System branch
Code Linting/Formatting
We use Megalinter to maintain our code by running various linters over the different file types we have apart from Python files (these are handled separately). This is run against PRs using the mega-linter GitHub action but can also be run locally. To run the linter locally you can run:
make megalint
This command will run all the linters enabled in the mega-linter.yml config file in the root of the repo against the all the files in the repo and report back any issues. This is run via docker and may take some time to run first time.
We also have another command which will also run Megalinter locally but this one will attempt to fix any issues it can rather than just report them.
make megalint-apply
More detailed documentation on the lint process is available in doc/linting-process.md.