RIDING BYTES
← All insights

December 30, 2024 · 10 min read

Install SENAITE LIMS on a fresh Ubuntu 24.04 server

A server install of SENAITE 2.x on Ubuntu 24.04: Python 2.7 with pyenv, the senaite.starter buildout, a systemd service, nginx with TLS, and backups. With the decisions to make first and the mistakes we see most.

Install SENAITE LIMS on a fresh Ubuntu 24.04 server

Docker is the right tool for a first look. A laboratory that keeps its data in SENAITE usually runs it as an ordinary service on a Linux server, where backups, updates and the network are handled the way the IT department handles everything else.

This guide builds that on Ubuntu 24.04 LTS: one SENAITE instance under systemd, reachable only through nginx with TLS, with nightly backups. Our own production setup runs on Debian and adds a ZEO cluster, a load balancer and monitoring. That setup ships as the SENAITE Enterprise VM. What follows is the part every SENAITE server needs.

Decide before you install

A few questions are cheaper to answer now than after go-live.

  • Python 2.7. SENAITE 2.x runs on Python 2.7, not on the Python 3 that Ubuntu ships. You build 2.7 for the SENAITE account with pyenv, separate from the system Python. If your IT policy forbids an unsupported interpreter on a server, settle that conversation first.
  • One process or several. A single instance serves a small laboratory well. Once several people regularly wait on reports or large worksheets, you want a ZEO server with several clients, one per CPU core. This guide sets up a single instance; the data layout carries over.
  • Host name and certificate. Most LIMS servers sit on an internal network. Then the certificate comes from your organisation’s own certificate authority. Let’s Encrypt only works if the server is reachable from the internet.
  • Timezone. It ends up in every timestamp SENAITE records. Set it before anything else.
  • Where backups go. A backup on the same disk protects you from a mistake, not from losing the machine. Decide now which other machine pulls the copies.

The base system

sudo timedatectl set-timezone Europe/Berlin
sudo apt update && sudo apt upgrade -y
sudo adduser --disabled-password --gecos "" senaite

The senaite account owns the code and the data and runs the instance. Nothing in this guide runs SENAITE as root. Buildout refuses to run as root anyway, and that refusal is right: a buildout run downloads code and executes it.

System libraries

SENAITE renders PDFs, parses XML and processes images through C libraries, and building Python 2.7 needs a few more. Install both sets, plus the fonts the PDF renderer uses:

sudo apt install -y git build-essential curl \
  libxml2 libxml2-dev libxslt1.1 libxslt1-dev \
  libcairo2 libpango-1.0-0 libpangocairo-1.0-0 \
  libgdk-pixbuf2.0-0 zlib1g zlib1g-dev libjpeg-dev \
  libbz2-dev libssl-dev libsqlite3-dev libffi-dev uuid-dev \
  libnss3-dev libgdbm-dev libgdbm-compat-dev libncursesw5-dev \
  liblzma-dev libreadline-dev \
  fonts-dejavu fonts-liberation

A report that looks right on your laptop and shows boxes instead of letters on the server is a missing font, every time.

Python 2.7 with pyenv

As the senaite user:

sudo -iu senaite
curl https://pyenv.run | bash

Put the pyenv setup in a file of its own and source it from the login shell:

cat > ~/.pyenv_profile <<'EOF'
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
EOF
echo '. ~/.pyenv_profile' >> ~/.bashrc
. ~/.pyenv_profile

Then build the interpreter and a virtual environment for SENAITE:

pyenv install 2.7.18
pyenv virtualenv 2.7.18 python2.7-senaite
pyenv activate python2.7-senaite
python -V        # Python 2.7.18

pyenv install compiles Python from source. If it stops with a message about a missing module, a -dev package from the list above is missing. Install it and run the command again.

Name the environment as shown, with the version before senaite. A name starting with python- collides with how pyenv 2.8 and newer resolve names.

The buildout

SENAITE is assembled with zc.buildout. The public starting point is senaite.starter. Its buildout.cfg pins a released SENAITE (2.6.0 at the time of writing) together with the matching core packages.

git clone https://github.com/senaite/senaite.starter.git ~/senaitelims
cd ~/senaitelims
pip install -r requirements.txt

Do not build buildout.cfg as it is. It contains two defaults that are fine on a laptop and wrong on a server: the instance listens on all network interfaces (0.0.0.0:8080), and the admin login is admin:admin. Put your changes into a file of your own, so that updating the starter does not overwrite them:

# ~/senaitelims/production.cfg
[buildout]
extends = buildout.cfg
parts += backup
senaite-user = admin:a-long-random-password

[instance]
http-address = 127.0.0.1:8080

[backup]
recipe = collective.recipe.backup

[versions]
collective.recipe.backup = 4.2.0
  • senaite-user sets the initial admin account. Set it before the first start. Changing it in the file later does not reliably change an existing account; change the password in the user folder of the instance root instead.
  • 127.0.0.1 means only processes on this machine reach the instance. nginx will be the only thing listening on the network.
  • The backup recipe is pinned because its newer versions require Python 3, and buildout would otherwise pick the newest one and fail.

Build it:

buildout -c production.cfg

The first run downloads every package and takes a while. Then start the instance once in the foreground:

bin/instance fg

When the log ends in Ready to handle requests, stop it with Ctrl+C. If it fails earlier, read the first traceback. A missing system library is the usual cause.

A systemd service

# /etc/systemd/system/senaite.service
[Unit]
Description=SENAITE LIMS
After=network.target

[Service]
Type=simple
User=senaite
Group=senaite
WorkingDirectory=/home/senaite/senaitelims
ExecStart=/home/senaite/senaitelims/bin/instance console
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now senaite
sudo systemctl status senaite

Two details matter here. The service uses console, not fg. Both keep the process in the foreground, which is what systemd expects, but fg also switches on debug mode, which you do not want in production. And the service does not need the pyenv profile: the scripts buildout writes into bin/ carry the full path of the interpreter they were built with.

nginx in front

sudo apt install -y nginx
# /etc/nginx/sites-available/senaite
server {
    listen 80;
    server_name lims.example.org;

    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080/VirtualHostBase/https/lims.example.org:443/senaite/VirtualHostRoot/;
        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 https;
        proxy_read_timeout 300s;
    }
}

The VirtualHostBase ... VirtualHostRoot path tells the instance which protocol, host name and port the user sees, and which site in the database to serve. Every link SENAITE generates then points at https://lims.example.org/... instead of 127.0.0.1:8080/senaite. The senaite in the middle is the path identifier you give the site when you create it.

The two other lines prevent the two complaints we hear most after a go-live. nginx rejects uploads above 1 MB by default, so a scanned document or an instrument file fails without a useful message. And publishing a batch of reports can take longer than nginx’s default 60 seconds, which looks to the user like a lost report.

Enable the site and add TLS:

sudo ln -s /etc/nginx/sites-available/senaite /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

For a server reachable from the internet, certbot fetches and renews a Let’s Encrypt certificate and adds the listen 443 block:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d lims.example.org

On an internal network, add a listen 443 ssl block with the certificate from your organisation’s authority instead. Do not leave users with a self-signed certificate: people who learn to click through browser warnings cannot tell a misconfiguration from an interception.

Create the site

Open the instance root through an SSH tunnel, so that it never has to be exposed:

ssh -L 8080:127.0.0.1:8080 you@lims.example.org

Then go to http://localhost:8080, click Create a new SENAITE site, log in with the admin account from production.cfg, and use senaite as the path identifier. After that, the site is at https://lims.example.org.

Backups

The backup part created bin/backup and bin/restore. The first run writes a full copy of the database and the attachments, later runs write increments, and by default two full backups are kept. The copy is consistent while the instance runs.

# crontab -e, as senaite
30 2 * * * cd /home/senaite/senaitelims && bin/backup

The backups land in var/backups and var/blobstoragebackups. The database and the attachments belong together: a restore that mixes generations gives you samples with missing or wrong attachments.

Then do the two things that make a backup worth having:

  • Copy var/ to another machine, pulled from there, so that a compromised or broken server cannot take the copies with it.
  • Restore it once on a second machine and open a sample with an attachment. Do it before go-live and once a year afterwards.

Packing

SENAITE’s database never overwrites. Every change appends a new revision, which is what makes the history of a record possible and what makes the file grow. Packing removes revisions older than a given number of days. On a single instance you pack in the management interface of the instance root, at /Control_Panel/Database/main/manage_main (through the SSH tunnel from above). With a ZEO setup, bin/zeopack does it from cron.

Pack after a backup, never because the disk is full: a pack needs room for a second copy of the file while it runs. And a pack cannot be undone. Revisions it removed are gone, while the audit log that SENAITE keeps as content stays.

What this setup still lacks

  • Monitoring. Whether last night’s backup and pack ran, whether the database is consistent, whether response times creep up. See Why your SENAITE LIMS needs proactive monitoring.
  • Updates. Ubuntu security updates on a schedule, and SENAITE updates with a test on a copy first. A new SENAITE version often brings upgrade steps that have to run after the build, before users work on the site again.
  • Several processes. A ZEO server with several clients and a load balancer, once one process is not enough.
  • Your configuration. Sample types, analysis services, specifications, the report layout. This is where most of the work of an introduction lies.

We do this work under SENAITE Care and in the 3-day workshop. If the server was the easy part and the rest looks like a project, talk to us.

Two people reviewing notes on paper next to a laptop

Bring this to your lab

Whether you are evaluating SENAITE, scaling it across sites, or wiring up instruments and monitoring, the team that wrote the platform is the team you talk to.