Skip to main content

Valens

/ˈva.lens/ [ˈväːlɛns] lat. strong, vigorous, healthy

Plan your training, follow it and see what it does to your body. A web app you host yourself, for you and anyone you share it with.

App screenshots

Features

  • Define training routines
  • Choose from more than 150 exercises or create your own
  • Plan your training days
  • Log repetitions, weight, time and rating of perceived exertion (RPE) for each set
  • Measure your training execution using a stopwatch, timer or metronome
  • Follow the current exercise and rest time in notifications
  • Calculate a one-repetition maximum (1RM) or the weights of a drop set
  • Assess the progress for each routine, exercise and muscle
  • Keep track of your body weight
  • Calculate and log your body fat based on the 3-site or 7-site caliper method
  • Follow your fat-free mass index (FFMI)
  • Monitor your menstrual cycle
  • Choose a light or dark theme, following your system by default
  • Install the app on your device and browse your data without a connection to the server
  • Share the app with several users, each with their own data
  • Protect accounts with passkeys, with one-time login links for recovery

Valens runs in current versions of Chromium-based browsers, Firefox and Safari.

Installation

Valens is available as a Python package and as a container image. The package uses the Python installation of the machine it runs on, the container brings everything with it.

Python Package

Valens requires Python 3.10 or newer. Install it into a virtual environment, as many distributions do not permit installing packages into the system Python. On Debian and Ubuntu, the python3-venv package is needed to create one.

python3 -m venv ~/.venvs/valens
source ~/.venvs/valens/bin/activate

The latest release can be installed from PyPI.

pip install valens

The latest development version can be installed from TestPyPI.

pip install --pre --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ valens

Activate the environment in every shell session that runs valens, or call the commands by their path, ~/.venvs/valens/bin/valens.

Container

The latest release is available as a container image on the GitHub Container Registry as ghcr.io/treiher/valens:latest. The latest development version is available under the dev tag as ghcr.io/treiher/valens:dev. The image is downloaded automatically when the container is started for the first time.

Running

Python Package

Demo mode runs without any setup. Regular use requires a config file, a user and a server.

Demo Mode

To get a first impression of Valens, the app can be run in demo mode.

valens demo

The app can be accessed on http://localhost:5000/. The names of the example users to sign in with are printed on startup. A temporary database with generated example data is used, containing training logs that cover every page of the app. All changes are non-persistent. Adding --public to the command line makes the app available to other devices on the network. It should only be used on a network you trust. A different port can be selected with --port.

Setup

A config file must be created before running the app for the first time.

valens config

By default, the config file is created in the current directory and the database at ~/.local/share/valens/valens.db. A different directory for the config file can be selected with -d, a different path for the database with --database. The database file is created automatically when the app or a CLI command accesses it for the first time. The preset PUBLIC_URL must be changed if the app is reachable under a different URL, as described under Public URL.

The environment variable VALENS_CONFIG must be set to the absolute path of the config file. All following commands expect it to be set, in every shell session and in the definition of a service running the app.

export VALENS_CONFIG=$PWD/config.py

A user must be created before signing in for the first time. Users sign in by entering their name, unless username login is disabled. Only users with the admin role are able to manage users in the app.

valens user create <name> <female|male> --role admin

The biological sex is used for the body fat calculation, the optional body height (--height) for the FFMI.

Users can also be listed, updated and deleted on the command line. See valens user --help for all available commands.

Local Use (Development Server)

The development server can be used to provide the app for the local computer or local network.

valens run

By default, the app is only accessible on the local computer at http://localhost:5000/. Adding --public to the command line makes the app available to other devices on the network:

valens run --public

It should only be used on a network you trust. A different port can be selected with --port.

The development server serves plain HTTP only, so the app is subject to the limitations described under Plain HTTP. It is not intended for use in a public network. See Deployment for providing the app there.

Container

The container image uses Gunicorn and listens on port 8000. A volume mounted at /app provides persistent storage for the database and configuration.

Docker / Podman

mkdir -p ~/valens
podman run -d --name valens -p 8000:8000 -v ~/valens:/app:Z,U ghcr.io/treiher/valens:latest

The app can be accessed on http://localhost:8000/. The database and the config file are stored in ~/valens.

The Z option of the volume relabels the directory for SELinux and can be omitted on systems without it. The U option adjusts the ownership of the directory to the user running the app in the container. When using Docker, replace podman with docker, omit :Z,U and make the directory writable for uid 1000.

A container created this way is not started again after a reboot. See Systemd (Quadlet) for starting it automatically. Continue with First Start.

Systemd (Quadlet)

The container can be managed as a systemd service using Podman Quadlet.

/etc/containers/systemd/valens.container

[Unit]
Description=Valens

[Container]
ContainerName=valens
Image=ghcr.io/treiher/valens:latest
PublishPort=8000:8000
Volume=/var/lib/valens:/app:Z,U
NoNewPrivileges=true
DropCapability=ALL

[Service]
Restart=on-failure

[Install]
WantedBy=multi-user.target

The app runs as an unprivileged user in the container and needs no capabilities. The volume options are described under Docker / Podman.

mkdir -p /var/lib/valens
systemctl daemon-reload
systemctl enable --now valens

First Start

A config file is created in the volume when the container is started for the first time. Its PUBLIC_URL must be changed as described under Public URL. For local use, this is the address of the published port.

PUBLIC_URL = 'http://localhost:8000'

The container must be restarted afterwards.

podman restart valens

When the container is managed by Quadlet, use systemctl restart valens instead.

A user must be created before signing in for the first time. Users sign in by entering their name, unless username login is disabled. Only users with the admin role are able to manage users in the app.

podman exec valens valens user create <name> <female|male> --role admin

All other CLI commands are run in the container in the same way, for example podman exec valens valens user login-link <name>. A container managed by systemd requires root privileges for these commands.

See Deployment for providing the app in a public network.

Gunicorn Settings

Gunicorn can be configured with the following environment variables:

  • GUNICORN_WORKERS: Number of worker processes (default: 2)
  • GUNICORN_THREADS: Number of threads per worker process (default: 1)
  • GUNICORN_TIMEOUT: Time in seconds after which an unresponsive worker process is restarted (default: 120)

Configuration

Valens is configured by a Python file. It is read when the server starts, so the server must be restarted after a change.

Public URL

PUBLIC_URL must be set to the URL under which the app is reachable by its users (e.g. https://valens.example.com), otherwise passkeys and one-time login links do not work. Passkeys additionally require HTTPS, unless the app is accessed via localhost. Behind a reverse proxy, it is the URL of the proxy, not the address of the server behind it.

The config file presets PUBLIC_URL to http://localhost:5000, which matches the port of the development server. As the container listens on port 8000, the preset must be adapted for it even if the app is only used locally.

With PUBLIC_URL set and the app served over HTTPS or accessed via localhost, users add a passkey to their account under Profile in the menu.

Additional Settings

  • PERMANENT_SESSION_LIFETIME: How long a user stays signed in, counted from the sign-in and not from the last request, so an active user is signed out at the deadline as well (default: timedelta(weeks=52)).
  • USERNAME_LOGIN_ENABLED: Whether signing in by entering a username without any credential is possible (default: True). If set to False, users sign in with a passkey and PUBLIC_URL must be set. The initial sign-in and the recovery of an account without a passkey is done via a one-time login link created by an admin in the app or with valens user login-link <name> on the command line.

Database

The database file holds the data of all users and is the only file that needs to be backed up to preserve them. It is located at ~/.local/share/valens/valens.db by default, or in the mounted volume when using the container. Copy the file while the server is stopped, or use sqlite3 valens.db ".backup backup.db" for a consistent copy of a database in use. In a rootless container, the files belong to a subordinate user ID and are reached with podman unshare.

The config file holds the secret key that signs the session cookies. Losing it signs out all users, but no data is lost.

Deployment

A production-grade server should be used for providing the app in a public network. See the deployment options for the alternatives. The Python package requires a config file and a user as described under Setup, created as shown below. PUBLIC_URL must be set as described under Public URL.

Serving the app over HTTPS is recommended. It is required for passkeys and for installing the app on a device, as described under Plain HTTP. The examples below show only the parts specific to Valens. TLS is configured in the surrounding server block.

Python Package

One option for an installation of the Python package is using NGINX and uWSGI. The package must be importable by uWSGI, which is achieved by installing it into a dedicated virtual environment.

python3 -m venv /opt/valens/venv
/opt/valens/venv/bin/pip install valens

The service user needs to read the config file and write the database. Both must be located outside the web root, as files below it are served directly.

mkdir -p /var/lib/valens
chown http:http /var/lib/valens
/opt/valens/venv/bin/valens config -d /var/lib/valens --database /var/lib/valens/valens.db
chown root:http /var/lib/valens/config.py
chmod 640 /var/lib/valens/config.py

This replaces the valens config command of the Setup. The other CLI commands must be run as the service user, so that the files they create stay accessible to it.

sudo -u http env VALENS_CONFIG=/var/lib/valens/config.py /opt/valens/venv/bin/valens user create <name> <female|male> --role admin

/etc/uwsgi/valens.ini

[uwsgi]
master = true
plugins = python
socket = /run/uwsgi/%n.sock
manage-script-name = true
mount = /=valens:app
uid = http
gid = http
virtualenv = /opt/valens/venv
env = VALENS_CONFIG=/var/lib/valens/config.py

/etc/nginx/nginx.conf

[...]

http {

    [...]

    server {

        [...]

        gzip on;
        gzip_types text/plain text/css text/javascript application/json application/wasm;

        location / { try_files $uri @valens; }
        location @valens {
            include uwsgi_params;
            uwsgi_pass unix:/run/uwsgi/valens.sock;
        }

    }

}

Container

A reverse proxy such as NGINX can be used to expose the container in a public network.

/etc/nginx/nginx.conf

[...]

http {

    [...]

    server {

        [...]

        gzip on;
        gzip_types text/plain text/css text/javascript application/json application/wasm;

        location / {
            proxy_pass http://127.0.0.1:8000;
        }

    }

}

NGINX Compression

Compression is disabled in NGINX by default and is enabled by the gzip directives in the examples above. With compression enabled, the amount of data transferred can be significantly reduced, resulting in a reduction in transfer time, especially on slow networks. Brotli offers better compression ratios than gzip and is supported as an optional NGINX module.

Upgrading

The database schema is upgraded automatically when the app is used for the first time after an update. A copy of the database is created next to it beforehand, named after the revision it is upgraded from and the time of the upgrade. The schema can also be upgraded explicitly, so that the upgrade does not happen during the first use of the app.

Python Package

Install the new version in the activated virtual environment and restart the server.

pip install --upgrade valens

The schema is upgraded explicitly with valens upgrade.

For a deployment as described under Deployment, use the pip and valens commands of its virtual environment and run the latter as the service user.

Container

Pull the new image, remove the container and create it again with the command that was used before.

podman pull ghcr.io/treiher/valens:latest
podman stop valens
podman rm valens
podman run -d --name valens -p 8000:8000 -v ~/valens:/app:Z,U ghcr.io/treiher/valens:latest

When the container is managed by Quadlet, restarting the service recreates it.

systemctl restart valens

The schema is upgraded explicitly with podman exec valens valens upgrade.

Limitations

Offline Use

Recording data requires a connection to the server. Every change is sent there first, and a change made while it is unreachable is reported as an error instead of being stored for later.

Plain HTTP

Browsers withhold a number of features from pages that are not served over HTTPS. Valens is usable over plain HTTP, but the following are not:

  • Installing the app on the device and starting it without a connection, which need a service worker
  • Signing in with a passkey, unless the app is accessed via localhost
  • Notifications outside the app, which are shown by the service worker
  • Copying a routine or a login link to the clipboard
  • Keeping the screen on while a timer is running

Serving the app over HTTPS enables all of them.

Background Use

The beeps of the timer and the metronome are produced in the browser, and notifications are triggered by the app while it runs. With the screen turned off or Valens in the background, they can be late, distorted or absent, depending on how aggressively the browser suspends a page that is not visible.

Documentation

License

This project is licensed under the terms of the AGPL-3.0 license and includes third-party software.

Metadata

Release files for valens 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for valens 0.8.0
File Size Uploaded
valens-0.8.0.tar.gz 1.9 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for valens 0.8.0
File Interpreter ABI Platform
valens-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.7 MB

Release files / valens-0.8.0.tar.gz

Download URL valens-0.8.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
f2cbc6a1916f06d7a7d9d38338185fb77113c7ab57695025ba1ebdbf53bb9a29
BLAKE2b-256 checksum
How to use checksums
e1c0396f1c75dff1eb4c5458a3d9f3e46ff99e82ed0ad0298d683c10371067cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release files / valens-0.8.0-py3-none-any.whl

Download URL valens-0.8.0-py3-none-any.whl
Size 1.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
53f39d6df78a62963e7a8f70ce687df492a14d4e697431fd0c22cd76badf76e0
BLAKE2b-256 checksum
How to use checksums
3181c9563ddf1079eccc338c17dd7304e89b6d0efacc5a2e3f81b701a4d46322
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release history Release notifications | RSS feed

0.9.0

2 release files

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page