My requirements.txt Has Exactly Two Lines: Pillow and reportlab. Here's What That Bought Me
I learned this the expensive way. I had uploaded a batch of 39 products when a CSV came back rejected, and then a second one. My pipeline had resubmitted the same batch twice because nothing tracked what was already posted, and I came close to losing the account before I had sold anything at all. The scripts I wrote that week are the reason I now have a requirements file with two lines in it, both of them chosen deliberately.
The two-line rule
Here is the whole file:
pillow
reportlab
Pillow renders the covers. reportlab renders the PDFs. Neither one is a framework, and neither one needs a cloud account. On a 1 vCPU, 2 GB server with no GPU, dependency weight is not a matter of taste. Every extra package is another thing that can fail at 2 a.m. while you're asleep.
The part that surprised me: make_metadata.py and upload_tracker.py don't need either package. They run on the standard library alone, no install at all. The two heaviest jobs in the pipeline turned out to be the two lightest, once I stopped reaching for a library first.
Platform CSVs reject you before a human ever sees your work
Each channel wants a different column layout, and none of them is forgiving. Adobe wants 5 columns, Vecteezy wants 4, Dreamstime wants 15. Get the header wrong by one field and the upload fails instantly, no review, no explanation.
make_metadata.py writes the header each platform demands and nothing else:
python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
If you pass --titles titles.json it uses real titles and keywords. Without it, the filename gets title-cased and used as a placeholder. That is fine for a first pass and wrong for a submission, so replace it before you hit upload.
Double posting is the fastest way to get flagged
This is the mistake from the top of this post, and the one I will not repeat. upload_tracker.py is a small ledger with three commands:
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Re-running a batch no longer double-posts, because the ledger already remembers. No clever design involved, just a file that refuses to forget.
The memory discipline that makes a 2 GB box viable
The full pipeline packaged 39 products, 856 MB of output, on a single core with 2 GB of RAM. It works because of a handful of habits:
- One process at a time. Never fan out image jobs in parallel on a 1-core box.
- Call
gc.collect()after each asset to free memory between items. - Measure peak RSS, not average.
resource.getrusage(RUSAGE_SELF).ru_maxrsstells you the real ceiling. - Process one file per subprocess for heavy render steps, so memory goes back to the OS.
- Render at the resolution you need. A preview at 60 dpi is enough for inspection.
None of this is clever. All of it is the difference between a pipeline that finishes and one that gets killed halfway through a batch.
The four failures the kit actually removes
Making the art is the fun part. These are the four steps that stall solo makers:
-
Metadata.
make_metadata.pywrites the exact CSV header each channel demands. -
Double posting.
upload_tracker.pymakes a repeat run impossible. -
Doc to product.
md2pdf.py content.md product.pdf "My Product Title"produces a styled PDF in one command. -
Packaging.
pack_product.py my-product "My Product" "A short subtitle" 9writes the zip, a 1280×720 cover, and a 600×600 thumbnail. A zip with a real cover sells. A bare file does not.
The kit and the rest of the pipeline
This layer runs on a free stack. Hermes Agent handles orchestration, retries, and scheduling at $0. 9Router routes the LLM calls. The scripts are the packaging layer that sits underneath it all.
The numbers behind the rules: 39 products packaged, about 19,000 lines of pipeline code in the parent project, and 300+ pages of research behind the constraints the scripts enforce. All of it validated on 1 vCPU, 2 GB of RAM, no GPU.
If your requirements file has grown past two lines, that might be the problem. You can drop the ones that do the CSV work entirely: make_metadata.py and upload_tracker.py need nothing but Python. The two lines that remain have earned their place.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)