Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ jobs:
- name: Verify the downloadable Node invoice and guide
run: python tools/verify_node_examples.py
- name: Verify the downloadable Python web starter and actual HTTP servers
run: python tools/verify_python_web_starter.py
run: python tools/verify_python_web_starter.py --docker
- uses: actions/upload-artifact@v4
if: always()
with:
Expand All @@ -171,6 +171,7 @@ jobs:
output/python-web-verification/*.json
output/python-web-verification/*.log
output/python-web-verification/checks
output/python-web-verification/docker
retention-days: 14
- uses: actions/setup-dotnet@v4
with:
Expand Down
Binary file modified docs/assets/python-web/invoice.pdf
Binary file not shown.
Binary file modified docs/assets/python-web/invoice.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/python-web/project.zip
Binary file not shown.
50 changes: 35 additions & 15 deletions docs/assets/python-web/source.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
{
"schema": "fullbleed.python-web-starter.v1",
"engineVersion": "2.5.6",
"engineVersion": "2.5.11",
"sourceRepository": "https://github.com/fullbleed-engine/fullbleed-official",
"sourceCommit": "76769ccd58426717c10cf0d988838182c203382f",
"sourceCommit": "5a604cb79854793b1e64b2e04ef6286d76a28760",
"files": [
{
"path": ".dockerignore",
"bytes": 372,
"sha256": "a9fd1d7bcf4f3ec1a04f179b80e91c1d82c1d08fef028a90d358ad2f92b6ab9c"
},
{
"path": ".gitignore",
"bytes": 28,
"sha256": "d3b4beec83000bfbd6f81222b601d367412f30ef10bf55a6bc51b275bdfd905f"
},
{
"path": "Dockerfile",
"bytes": 984,
"sha256": "706596fcd098b3ccb667e0d2b69d46e253ce2d6a7291fbdce79cf79b3583c190"
},
{
"path": "LICENSE",
"bytes": 1079,
"sha256": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9"
},
{
"path": "README.md",
"bytes": 5145,
"sha256": "b70edbd2649dbf4ea64ebeedaba6522ef8b484c02309febfde22be8babd99b78"
"bytes": 8604,
"sha256": "1c310b4e57a0795532673c131374e8ea11f134bdb091cee90e2b648fcada8b0e"
},
{
"path": "check_docker.py",
"bytes": 13706,
"sha256": "ccb96de543f66e741cee69ccb9468bcc589d623925d2937e96589755dfb3591f"
},
{
"path": "check_examples.py",
"bytes": 6388,
"sha256": "2e675d4b0a1d55cba31fa0dd8d4f1eb6ce779af32ade71b90c2a0b2c63988628"
"bytes": 6422,
"sha256": "b5f4b1c73fc1453e2701aebbd3af877aa8616b045317b7d6694836c735f661ba"
},
{
"path": "check_http.py",
Expand Down Expand Up @@ -99,6 +114,11 @@
"bytes": 15,
"sha256": "56cbd1c0a43496327d9af1bf231abd19bb1b0e2f0a14734319fe3108dda90b13"
},
{
"path": "requirements-docker.txt",
"bytes": 406,
"sha256": "767e832458f822f62042cc7d31e212eb78449e27f53215b04e7887490293d1ef"
},
{
"path": "requirements-fastapi.txt",
"bytes": 33,
Expand All @@ -116,8 +136,8 @@
},
{
"path": "static/invoice.png",
"bytes": 130289,
"sha256": "a38d23651c355d1b87bc16fa37fe9121a377a12e6d515b4cd1871750c2241175"
"bytes": 129914,
"sha256": "acb15ac236a84a98c460fb0b55115093e64ab44ce7cd232e58b7b621cad731fa"
},
{
"path": "templates/invoice.css",
Expand All @@ -130,22 +150,22 @@
"sha256": "c8c522d0f983c9917587083e5472af39c515c447b33ebf3edf2fafcf2f21673e"
}
],
"checkedAt": "2026-10-04T23:24:23.430920+00:00",
"checkedAt": "2026-10-07T01:39:11.130140+00:00",
"assets": [
{
"file": "project.zip",
"bytes": 230094,
"sha256": "6bcd44c551aeace4e658bf96b7528ea7237d386dfebd52de5d48bbdab4712f1c"
"bytes": 238116,
"sha256": "59561aa6e40146d813cde78e9f0d25b2c56ebac2ee4dc171ce06206f49504386"
},
{
"file": "invoice.pdf",
"bytes": 91537,
"sha256": "2e908a21e5f9ac5b1fa761d678833c62f3acc772ba7162d8da1b900f861320e6"
"bytes": 40277,
"sha256": "3c70915599a7e88b93203f1e64e269231621a0105db90ee31f5f762e1b138057"
},
{
"file": "invoice.png",
"bytes": 130289,
"sha256": "a38d23651c355d1b87bc16fa37fe9121a377a12e6d515b4cd1871750c2241175"
"bytes": 129914,
"sha256": "acb15ac236a84a98c460fb0b55115093e64ab44ce7cd232e58b7b621cad731fa"
}
]
}
1 change: 1 addition & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ their own roles. Grid and table layouts keep the page ordered.

Start with the [invoice guide](guides/invoices.md), or
[serve an invoice from FastAPI, Flask, or Django](guides/web-frameworks.md).
The Python starter also includes a [tested Docker image](guides/python-docker.md).
For C# web applications, try the [ASP.NET Core download starter](guides/aspnet-pdf.md).

</div>
Expand Down
10 changes: 9 additions & 1 deletion docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,20 @@ python -m pip install fullbleed

The stable-ABI wheels cover supported CPython versions. For an unsupported target, a source build requires Rust; [source build instructions](https://github.com/fullbleed-engine/fullbleed-official#install) are in the engine repository.

## Run in Docker

The [Docker invoice starter](../guides/python-docker.md) includes a FastAPI app,
editable HTML/CSS, bundled fonts, and a Dockerfile tested on Debian slim and
Alpine on x64 and ARM64. It installs wheels with `--only-binary=:all:` and runs
as a non-root user with a read-only filesystem. Use it to check your container
integration before adapting the renderer to your application.

## Troubleshooting

| Symptom | Next step |
| --- | --- |
| `fullbleed` command is not found | Use `python -m fullbleed` from the environment where you installed it. |
| pip starts a Rust build | Upgrade pip and check that your Python/platform has a published wheel. |
| pip starts a Rust build | Upgrade pip and check that your Python/platform has a published wheel. Use `--only-binary=:all:` to require one; the [Docker starter](../guides/python-docker.md) demonstrates this in slim and Alpine images. |
| Permission or externally managed environment error | Install inside a virtual environment. |
| Missing characters | Register a font containing those characters; see [fonts and assets](../engine/assets.md). |

Expand Down
136 changes: 136 additions & 0 deletions docs/guides/python-docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
title: Generate PDF invoices in Docker with Python
description: Run a FastAPI PDF endpoint in Debian slim or Alpine using Fullbleed wheels, bundled fonts, a non-root user, and a read-only filesystem. Includes a tested downloadable starter.
---
# Generate PDF invoices in Docker with Python

The Python invoice starter includes a Dockerfile that serves a designed PDF from
FastAPI. It installs prebuilt wheels, carries its fonts with the application, and
runs without adding a compiler, browser, or system PDF renderer.

[Download the Docker-ready Python starter](../assets/python-web/project.zip){ .md-button .md-button--primary }
[Open the sample PDF](../assets/python-web/invoice.pdf){ .md-button }

This is the same editable HTML/CSS project used in the
[FastAPI, Flask, and Django guide](web-frameworks.md). Its container starts the
FastAPI adapter. The included invoice is fictional and has three line items;
connect your application's authorized lookup before serving real records.

## Build and download an invoice

Use Docker with Linux containers. Extract the ZIP and open its
`fullbleed-python-invoice` directory, then build the image:

```bash
docker build --tag fullbleed-invoice .
```

Run the server with a loopback-only published port:

```bash
docker run --rm --name fullbleed-invoice --publish 127.0.0.1:8000:8000 --read-only --cap-drop ALL --security-opt no-new-privileges:true --memory 512m --cpus 2 --pids-limit 128 fullbleed-invoice
```

These single-line commands work in PowerShell too. Open
`http://127.0.0.1:8000/` and select **Download invoice**, or request
`http://127.0.0.1:8000/invoices/INV-1042.pdf` directly. The response is an
`application/pdf` attachment with `Cache-Control: private, no-store`.

[![Northstar Studio invoice with large green typography, a three-item table, and a total of USD 1,870.00.](../assets/python-web/invoice.png)](../assets/python-web/invoice.pdf)

Stop it from another terminal:

```bash
docker stop fullbleed-invoice
```

Edit `templates/invoice.html`, `templates/invoice.css`, or `invoice.py`, rebuild
the image, and start it again to use your changes. The local source files are
copied into the image; this command does not mount them from your machine.

## What the image contains

| Component | Included configuration |
| --- | --- |
| Python | Official Python 3.14.8 Debian Bookworm slim image, pinned by multi-platform digest |
| PDF engine | Published Fullbleed 2.5.11 wheel |
| HTTP server | FastAPI 0.142.2 and Uvicorn 0.54.0; one worker, proxy headers disabled |
| Dependencies | Exact runtime versions in `requirements-docker.txt`; wheel URLs and hashes in `/app/pip-install.json` |
| Fonts | Inter from the wheel; vendored DM Serif Display and Bebas Neue with their licenses |
| Application user | UID/GID 10001; source files owned by root |
| Build context | Explicit `.dockerignore` allowlist for the application, templates, fonts, and dependency files |

The install command uses `--only-binary=:all:`. If an appropriate wheel is
unavailable, pip stops with an installation error instead of attempting a Rust
or C build. The official Python image documentation also explains why source
builds can fail in a slim image without development tools. See
[Docker's Python image reference](https://hub.docker.com/_/python).

The run command makes the filesystem read-only, drops Linux capabilities,
disallows privilege escalation, and sets memory, CPU, and process limits. The
renderer returns PDF bytes in memory; concurrent requests do not share output
filenames. Builds fetch dependencies, while this render uses only local assets.
See [Docker's runtime options](https://docs.docker.com/engine/containers/run/)
for the behavior of these settings.

## Alpine and ARM64

The same Dockerfile accepts an alternate official Python image:

```bash
docker build --build-arg PYTHON_IMAGE=python:3.14.8-alpine3.24@sha256:f6a589d43c42b9e7f7dc67a12d37132491f362859a5d750607710cc56da3bc72 --tag fullbleed-invoice-alpine .
```

Use `fullbleed-invoice-alpine` as the image in the run command. The pinned slim
and Alpine image references include Linux x64 and ARM64 variants; Docker selects
the platform for your daemon.

[Container CI](https://github.com/fullbleed-engine/fullbleed-official/actions/workflows/python-docker-example.yml)
builds and runs both images on native x64 and ARM64 runners. It checks actual
downloads, four simultaneous responses, HTTP 404/405 behavior, independently
extracted PDF text and embedded fonts, the native preview, and normal server
shutdown. A build-context check plants dummy secret files at the root and inside
asset directories, then verifies that Docker excludes them. It also renders in a
separate container with networking disabled.
All four resulting PDFs and previews must match.

Each run retains the PDF, PNG, build/server logs, resolved packages, wheel hashes,
image ID, and runtime settings. The documentation workflow additionally builds
and exercises the default image from the exact downloadable ZIP.
The [source record](../assets/python-web/source.json) identifies that ZIP and its
sample assets by hash.

## Repeat the checks locally

With a local Linux-container Docker daemon and Python on the host, run from the
extracted starter directory:

```bash
python -m pip install pypdf==6.19.0
python check_docker.py --out output/docker-check
```

The output directory must be new. The script removes only its own uniquely named
containers and image tag; Docker's shared image/build cache stays under your
control. To check Alpine, pass the same pinned image reference using
`--base-image`. `pypdf` is a host-side verification dependency, separate from the
application image.

## Connect it to your deployment

Replace the fictional `load_invoice()` with your application's authorized record
lookup. Configure TLS, trusted reverse proxies, request limits, monitoring, and
worker or job-queue capacity in your hosting setup. The example's resource limits
are test settings, not a throughput or sizing recommendation. The route has no
authentication, and the fixed one-page layout needs pagination work for long
invoices.

Update the pinned image and dependency versions periodically, then rebuild and
rerun the checks. These checks establish the behavior of the included container
and fixture; they do not establish production capacity or PDF standards
conformance. For larger document jobs, use a queue and store the finished PDF;
the [variable-data guide](bank-statements.md) covers compiled document families.

[Python installation](../getting-started/installation.md) ·
[FastAPI, Flask, and Django](web-frameworks.md) ·
[Editable invoice data](invoices.md)
16 changes: 10 additions & 6 deletions docs/guides/web-frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ the exact source files, fonts, package version, and generated sample. This is a
fictional integration example; connect your application's authorized record
lookup before serving real invoices.

Want a container? The same download includes a [tested Docker path](python-docker.md)
for the FastAPI app, using Debian slim or Alpine.

## Set up the examples

Use Python 3.10–3.14. Extract the starter, open its `fullbleed-python-invoice`
Expand All @@ -36,7 +39,7 @@ is `examples/web_frameworks`.

Choose one framework below. Each requirement file installs that framework;
`fullbleed` remains a separate package. The examples are checked with Fullbleed
2.5.6, FastAPI 0.142.2, Flask 3.1.3, and Django 5.2.17.
2.5.11, FastAPI 0.142.2, Flask 3.1.3, and Django 5.2.17.

Each app serves this fictional record:

Expand All @@ -52,7 +55,7 @@ returns HTTP 404. Stop the server before trying another framework on the same po
## FastAPI

```bash
python -m pip install fullbleed==2.5.6 -r requirements-fastapi.txt
python -m pip install fullbleed==2.5.11 -r requirements-fastapi.txt
python -m uvicorn fastapi_app:app --host 127.0.0.1 --port 8000
```

Expand Down Expand Up @@ -88,7 +91,7 @@ finishes. See [FastAPI's concurrency documentation](https://fastapi.tiangolo.com
## Flask

```bash
python -m pip install fullbleed==2.5.6 -r requirements-flask.txt
python -m pip install fullbleed==2.5.11 -r requirements-flask.txt
python -m flask --app flask_app run --host 127.0.0.1 --port 8000
```

Expand Down Expand Up @@ -116,7 +119,7 @@ for response bodies, media types, and headers.
## Django

```bash
python -m pip install fullbleed==2.5.6 -r requirements-django.txt
python -m pip install fullbleed==2.5.11 -r requirements-django.txt
python django_app.py runserver 127.0.0.1:8000 --noreload
```

Expand Down Expand Up @@ -219,8 +222,9 @@ invoice** link always renders the current HTML/CSS. Verification covers these
synthetic fixtures and local servers; it does not establish production capacity
or PDF standards conformance.

The launch commands above run local development servers. Use your framework's
deployment setup for a public app. For large jobs, render in your job queue and
For the FastAPI app, follow the [Docker deployment guide](python-docker.md).
The Flask and Django commands above run local development servers; use your
framework's deployment setup for a public app. For large jobs, render in your job queue and
store the finished PDF for download. The [variable-data guide](bank-statements.md)
covers compiled document families when many records share a template.

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ nav:
- Rebuild as you edit: guides/render-watch.md
- Check PDFs in CI: guides/pdf-regression-ci.md
- FastAPI, Flask, and Django: guides/web-frameworks.md
- Python in Docker: guides/python-docker.md
- Browser PDF generation: guides/browser-pdf.md
- React PDF previews: guides/react-pdf.md
- Vue PDF previews: guides/vue-pdf.md
Expand Down
Loading
Loading