1. What a System Package Is
Most Python libraries install with one pip install command. Some libraries are only a thin Python layer over a separate program. The pytesseract library calls the Tesseract program. The pydub library calls ffmpeg. Selenium drives a real web browser. Pip installs the Python layer, but it cannot install the program underneath.
Those programs come from the operating system's package manager. On Debian and Ubuntu, that manager is apt. Developers call these programs system packages or apt packages.
On your laptop, you may have installed ffmpeg or Tesseract months ago and forgotten about it. Your app works at home and then fails on a server that has never seen those programs. This guide explains how to list system packages for your host, which ten packages Python apps need most often and how to fix the errors that show a package is missing.
2. Signs Your App Needs One
A missing system package almost never says "missing system package." It shows up as one of the errors below. If you see one in your logs, the right column names the fix.
| Error message | Library | Add to packages.txt |
|---|---|---|
| FileNotFoundError ... 'ffmpeg' | pydub, Whisper, moviepy | ffmpeg |
| TesseractNotFoundError | pytesseract | tesseract-ocr |
| PDFInfoNotInstalledError | pdf2image | poppler-utils |
| cannot find Chrome binary | Selenium | chromium, chromium-driver |
| libGL.so.1: cannot open shared object file | OpenCV | libgl1, libglib2.0-0t64 |
| cannot load library 'pango-1.0-0' | WeasyPrint | libpango-1.0-0, libpangoft2-1.0-0 |
| FileNotFoundError ... 'soffice' | DOCX to PDF scripts | libreoffice-writer-nogui |
| ExecutableNotFound ... 'dot' | graphviz, pydot | graphviz |
| Can not find valid pkg-config name | mysqlclient | default-libmysqlclient-dev, pkg-config |
| Could not find the GDAL library | GeoDjango | gdal-bin, libproj-dev, binutils |
The pattern is the same in every row. Python found its own library, and that library could not find the program or file it depends on.
3. How packages.txt Works
On Cloudilio, you list system packages in a plain text file called packages.txt. Put the file in the same folder as your requirements.txt. Write one Debian package name per line.
# packages.txt
# Audio and video
ffmpeg
# Reading text in scanned images
tesseract-ocr
When you publish, Cloudilio reads the file and installs each package with apt-get. This happens before pip installs your Python packages. A library that needs a system package while it installs, such as mysqlclient, finds it ready.
A few rules apply to the file.
- Lines that start with
#are comments. Cloudilio skips them. - You can list up to 50 packages.
- Names may use lowercase letters, numbers and the characters
+,-and.. Cloudilio stops the build and names the line if it finds anything else. - A file called
apt.txtworks the same way. If you have both files, Cloudilio installs the packages from both.
Streamlit Community Cloud uses the same packages.txt file. If you move a Streamlit app from there, your file works without changes.
The publish log shows a line such as Installing system packages: ffmpeg, tesseract-ocr. If a name is wrong, the log shows apt's own message, and the version of your app that is live now stays live. The system packages guide in the docs covers every step and error in detail.
4. Ten System Packages Python Apps Need
These ten packages cover most of the requests we see. Each entry explains what the package does, which Python libraries use it, the exact lines for packages.txt and a short example to test it.
1. ffmpeg for audio and video
ffmpeg reads, converts, cuts and compresses almost every audio and video format. Many Python libraries hand their media work to it.
- Used by pydub, moviepy, OpenAI Whisper, yt-dlp and ffmpeg-python.
- Common jobs include converting uploads to MP3, making video thumbnails, transcribing speech and shrinking large files.
# packages.txt
ffmpeg
This example converts any uploaded audio file to MP3 with pydub.
from pydub import AudioSegment
audio = AudioSegment.from_file("upload.m4a")
audio.export("upload.mp3", format="mp3", bitrate="128k")
You can also call ffmpeg directly. This command saves the frame at one second as a thumbnail.
import subprocess
subprocess.run(
["ffmpeg", "-y", "-i", "clip.mp4", "-ss", "1", "-frames:v", "1", "thumb.jpg"],
check=True,
timeout=120,
)
Video work uses a lot of CPU. Give every ffmpeg call a timeout so one broken file cannot hold your app forever.
2. tesseract-ocr for reading text in images
Tesseract is an open source OCR engine. OCR stands for optical character recognition. It turns a photo of text, such as a receipt or a scanned letter, into text your code can search and store.
- Used by pytesseract, OCRmyPDF and many invoice and receipt scanners.
- Common jobs include reading receipts, making scanned PDFs searchable and pulling numbers from screenshots.
# packages.txt
tesseract-ocr
# Optional extra languages, named by their three-letter code
tesseract-ocr-deu
tesseract-ocr-fra
import pytesseract
from PIL import Image
text = pytesseract.image_to_string(Image.open("receipt.png"), lang="eng")
print(text)
The base package reads English. Add one tesseract-ocr- package for each other language you need, then pass the codes to lang, for example lang="eng+deu". Clear, high-contrast images give much better results than blurry phone photos.
3. poppler-utils for working with PDFs
Poppler is a PDF toolkit. The poppler-utils package adds small command-line programs such as pdftoppm, which turns PDF pages into images, and pdftotext, which pulls out the text.
- Used by pdf2image and many document-processing scripts.
- Common jobs include page previews, PDF thumbnails and sending scanned PDFs to Tesseract.
# packages.txt
poppler-utils
from pdf2image import convert_from_path
pages = convert_from_path("report.pdf", dpi=150, first_page=1, last_page=1)
pages[0].save("report-preview.png")
Poppler and Tesseract work well together. Turn each PDF page into an image with pdf2image, then read the image with pytesseract. Set first_page and last_page for large files, because each page at high resolution takes a lot of memory.
4. chromium and chromium-driver for browser automation
Some jobs need a real web browser. Scraping a site that builds its pages with JavaScript is one. Taking a screenshot of a web page is another. Chromium is the open source browser behind Google Chrome. The chromium-driver package adds ChromeDriver, which lets Selenium control it.
- Used by Selenium, and Playwright when you point it at a browser you installed yourself.
- Common jobs include scraping, page screenshots, HTML to PDF and automated checks of your own site.
# packages.txt
chromium
chromium-driver
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
options = webdriver.ChromeOptions()
options.binary_location = "/usr/bin/chromium"
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(service=Service("/usr/bin/chromedriver"), options=options)
driver.get("https://example.com")
driver.save_screenshot("example.png")
driver.quit()
The three options matter on a server. --headless=new runs the browser without a screen. --no-sandbox lets Chromium start inside a container. --disable-dev-shm-usage stops crashes caused by the small shared-memory area in containers.
A browser needs memory. Plan for at least 1 GB, and always call driver.quit(), even after an error. A browser you forget to close keeps using memory until your app restarts.
5. libgl1 for OpenCV
OpenCV is the most popular computer vision library. Its standard Python wheel, opencv-python, expects the graphics libraries that a desktop computer has. A server image does not have them, so the import fails with this error.
ImportError: libGL.so.1: cannot open shared object file: No such file or directory
- Used by OpenCV, Ultralytics YOLO, many face and object detection projects and some image tools.
- Common jobs include resizing and cropping images, detecting faces and running vision models.
You have two fixes. The better fix needs no system package at all. Replace opencv-python with opencv-python-headless in your requirements.txt. The headless version has the same features, without the parts that draw windows on a screen.
# requirements.txt
opencv-python-headless
Some libraries, such as Ultralytics, install the full opencv-python themselves. In that case, add the graphics libraries to packages.txt.
# packages.txt
libgl1
libglib2.0-0t64
Older guides list libglib2.0-0. Debian 13 renamed that package to libglib2.0-0t64, and the old name no longer installs.
6. Pango libraries for WeasyPrint PDFs
WeasyPrint turns HTML and CSS into PDF files. It is a popular way to make invoices, tickets and reports, because you design them with the same HTML you already use. WeasyPrint uses the Pango library to lay out text.
- Used by WeasyPrint, and Django and Flask apps that send PDF invoices or reports.
- Common jobs include invoices, receipts, certificates and printable reports.
# packages.txt
libpango-1.0-0
libpangoft2-1.0-0
libharfbuzz-subset0
from weasyprint import HTML
html = "<h1>Invoice 1042</h1><p>Total: $59.00</p>"
HTML(string=html).write_pdf("invoice-1042.pdf")
These three names come from WeasyPrint's own install guide for Debian. Without them, the import fails with an error that says WeasyPrint cannot load a library such as pango-1.0-0 or gobject-2.0-0.
7. libreoffice-writer-nogui for Word to PDF
Many business apps receive Word documents and need PDFs. Python has no reliable way to lay out a DOCX file by itself. LibreOffice does it well, and it can run without a screen. The -nogui package installs the word processor without any desktop parts.
- Used by document converters, contract tools and apps that turn uploads into PDFs.
- Common jobs include DOCX to PDF, ODT to PDF and DOCX to plain text.
# packages.txt
libreoffice-writer-nogui
import subprocess
subprocess.run(
["soffice", "--headless", "--convert-to", "pdf", "--outdir", "converted", "contract.docx"],
check=True,
timeout=120,
)
LibreOffice is the largest package on this list. It adds several hundred megabytes to your app and makes each build slower. It also handles one conversion at a time per user profile. If two requests convert at once, run conversions from a queue or a scheduled task instead of inside the web request.
8. graphviz for diagrams
Graphviz draws graphs, such as flowcharts, family trees, network maps and database diagrams. You describe the boxes and arrows, and Graphviz places them for you.
- Used by the graphviz package, pydot, diagrams and the
graph_modelscommand in django-extensions. - Common jobs include workflow charts, dependency maps and architecture diagrams in reports.
# packages.txt
graphviz
import graphviz
flow = graphviz.Digraph()
flow.edge("Sign up", "Welcome email")
flow.edge("Welcome email", "First login")
flow.render("onboarding", format="png", cleanup=True)
The Python graphviz package only writes instructions. The dot program from the system package draws the image. If you use pygraphviz instead, add libgraphviz-dev too, because that library compiles against Graphviz when pip installs it.
9. default-libmysqlclient-dev for MySQL
Django's recommended MySQL driver is mysqlclient. It is fast because it is written in C. That also means pip has to compile it, and the compiler needs the MySQL client library and the pkg-config tool.
- Used by mysqlclient, which Django and SQLAlchemy use for MySQL and MariaDB.
- Common jobs include connecting an app to an existing MySQL or MariaDB database.
# packages.txt
default-libmysqlclient-dev
pkg-config
Without these packages, pip install mysqlclient fails with a message such as Can not find valid pkg-config name or mysql_config not found. If you prefer to skip system packages, use PyMySQL. It is pure Python, so pip installs it anywhere. It is slower, but most small apps will not notice.
10. GDAL and PROJ for maps and GeoDjango
GeoDjango adds maps and location queries to Django. It can answer questions such as "which stores are within 5 km of this address?" It depends on three geographic libraries. GDAL reads map file formats. PROJ converts between coordinate systems. GEOS handles shapes, and it comes with GDAL.
- Used by GeoDjango, and Python GDAL bindings.
- Common jobs include store finders, delivery zones and maps of user data.
# packages.txt
binutils
libproj-dev
gdal-bin
# Lets GeoDjango use SQLite, through SpatiaLite
libsqlite3-mod-spatialite
The first three names come from Django's own install guide for Debian. The fourth lets GeoDjango work with SQLite, so you can store map data in the same database file as the rest of your app.
# settings.py
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.spatialite",
"NAME": os.path.join(os.environ["APP_DATA_DIR"], "db.sqlite3"),
}
}
Without these packages, Django stops at startup with Could not find the GDAL library. If you only need to read map files with geopandas, try it without system packages first. Recent geopandas installs bring their own copy of GDAL.
5. Two Small Extras
git, for packages installed from a repository
Some requirements.txt files install a package straight from GitHub with a line such as git+https://github.com/owner/project.git. Pip needs the git program to download it. Add git to packages.txt, or better, pin a released version from PyPI.
fonts-noto-cjk, for Chinese, Japanese and Korean text
Server images include very few fonts. Charts from matplotlib and PDFs from WeasyPrint show empty boxes instead of Chinese, Japanese or Korean characters. The fonts-noto-cjk package adds Google's Noto fonts for those languages. The package is large, so add it only when you need it.
6. Packages You Do Not Need to List
Every Cloudilio app already includes three system packages. You do not need to add them to packages.txt.
- build-essential provides the C compiler that pip uses to build packages without a ready-made wheel.
- libpq-dev lets
psycopg2connect to PostgreSQL. - curl downloads files from the command line.
Python itself, pip and SQLite also come with every app. Most popular libraries, such as NumPy, pandas, Pillow and cryptography, ship as ready-made wheels and need nothing extra.
7. Find the Right Package Name
Package names differ between Linux systems. A guide written for Ubuntu, Alpine or macOS may use a name that Debian does not have. Use these steps to find the right one.
- Read the install section of the Python library's own docs. Look for a line that starts with
apt installorapt-get install, and copy the names after it. - Search packages.debian.org for Debian 13, also called trixie. The official Python images use this release today.
- If an error names a missing file, such as
libGL.so.1, use the "search the contents of packages" form on the same site. It tells you which package contains that file.
Watch for two naming patterns. Packages that end in -dev contain files for compiling code. You need them only when pip builds a library from source, as with mysqlclient. Packages without -dev contain what a program needs to run. Wheels from PyPI usually need only the second kind.
8. Keep Builds Small and Fast
Every package you list makes your build slower and your app larger. A few habits keep both under control.
- List only what your code uses. Remove packages left over from experiments. Fewer packages also means fewer security updates to worry about.
- Prefer a pure Python or headless library when one exists.
opencv-python-headlessandPyMySQLare two examples. - Add a comment above each package that says why it is there. The next person to read the file will thank you.
- Test with a preview first. A preview builds and starts the new version without changing your live site. If a package name is wrong, only the preview fails.
- Watch your memory. Chromium, LibreOffice and large Whisper models need more than the 256 MB in the Starter plan. The Developer plan includes 1 GB.
Each build has a 15-minute time limit and a 5 GB size limit. Ffmpeg, Tesseract and the other packages in this guide fit well inside both. LibreOffice and the CJK fonts are the largest, so keep an eye on the build time in the publish log if you add both.
9. Questions People Ask
Can I run apt-get install from my app's code?
No. Your app runs as a normal user without administrator rights, which keeps it safer. Install system packages at build time with packages.txt instead.
Can I pick a specific version of a system package?
Not at the moment. Cloudilio installs the version that Debian ships in its current release. Debian keeps those versions stable and adds security fixes, so they suit most apps well.
Why does my app work at home but not after I publish?
Your computer probably has the program already, installed by you or by another tool. The server starts from a clean system every time. Check your logs for one of the errors in the table at the top of this guide.
Does packages.txt from Streamlit Community Cloud work on Cloudilio?
Yes. Both services read the same file name and the same Debian package names. Copy your repository as it is.
What happens if I spell a package name wrong?
The build stops, and the publish log shows apt's message, such as Unable to locate package. The version of your app that is live now keeps running. Fix the name and publish again.
Cloudilio Engineering Team
Application Delivery Group • CloudilioCloudilio hosts Python apps. You publish Flask, Django, FastAPI, Streamlit and plain Python scripts without managing a server, and your app's data stays safe between updates.