DEV Community

Cover image for The Real Costs Behind Near-Zero AI Image Generation: One Server, Two Packages, 39 Products

The Real Costs Behind Near-Zero AI Image Generation: One Server, Two Packages, 39 Products

The first batch I uploaded to Adobe got rejected before a human ever saw the art. Not for quality. The CSV header had five columns in the wrong order. I had spent two nights on the images and about forty seconds on the file that actually delivered them. That was the moment I stopped treating the packaging as an afterthought.

I make stock vectors and stickers with a small automated pipeline. So far it has produced 1,700 assets, 58 are live on Adobe, and the first sale was $0.99. That last number is not a humblebrag, it is the actual economics of starting out, and it is why every cost below is measured in RAM, rejections, and hours rather than dollars.

Here is what the pipeline actually costs to run, and what I got wrong on the way.

Metadata is the filter, not the art

Every marketplace rejects a malformed CSV instantly. Adobe wants a 5 column header. Vecteezy wants 4. Dreamstime wants 15. Get the column count or the order wrong and the whole batch bounces, no partial credit, no error explaining which column was off.

I wrote make_metadata.py for exactly this. Point it at a folder, tell it the platform, get the header that 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 vecteezy --out vecteezy.csv --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

Without a titles file it title-cases the filename, which is fine for a first pass and wrong for a submission. Real titles and keywords go in via --titles titles.json. I learned that the hard way too: filename-derived keywords are generic, generic keywords do not rank, and unranked assets do not sell. The 58 live assets on Adobe are the ones where I wrote real titles.

Double-posting is the fastest way to lose an account

Running a batch twice is a two keystroke mistake. I made it once on Vecteezy. Nothing catastrophic happened, but it flagged the upload pattern, and that is a bad place to be as a new seller.

upload_tracker.py is a ledger. It remembers what is done.

python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

A re-run can no longer double-post, because the script knows what already went out. This is boring code, roughly a flat file and a set difference, and it is the single highest value script in the kit. Preventing one account flag is worth more than any speed optimisation I have made.

The memory ceiling is real and it is 2 GB

The whole pipeline runs on 1 vCPU, 2 GB RAM, no GPU. That is not a constraint I chose for aesthetics, it is what I had. It works, but only with habits that are not optional on one core:

  • One process at a time. I fanned out eight image jobs once and the box swapped until the kernel killed the parent. Nothing was corrupted, but I lost the batch and the tracker had no record of it.
  • gc.collect() after each asset. Freeing memory between items keeps peak down.
  • Measure peak RSS, not average. resource.getrusage(resource.RUSAGE_SELF).ru_maxrss gives the real ceiling. Averages hide the spike that kills you.
  • One file per subprocess for heavy render steps, so memory returns to the OS instead of accumulating in the parent.
  • Render at the resolution you need. Previewing at 60 dpi instead of 300 cut one inspection step from minutes to seconds.

Those five habits are the difference between a pipeline that runs unattended overnight and one that dies at item 200 of 400.

Packaging is a product decision

A zip with a real cover sells. A bare file does not. pack_product.py does both in one command:

python pack_product.py my-product "My Product" "A short subtitle" 9
Enter fullscreen mode Exit fullscreen mode

That writes a zip, a 1280×720 cover, and a 600×600 thumbnail. Covers are Pillow. PDFs, when the product is a doc rather than art, come from md2pdf.py and reportlab. Those are the only two third party packages in the entire kit. make_metadata.py and upload_tracker.py use the standard library alone, so you can run them with no install at all.

Two packages is a deliberate ceiling. Every dependency is something that can break on a server you cannot debug interactively at 2am.

The actual numbers

39 products packaged, 856 MB of output. About 19,000 lines of pipeline code in the parent project. Over 300 pages of research behind the rules the scripts enforce, and that research folder stays on disk forever, because it is the raw material for the next product. I deleted mine once to clean up. I will not do it again.

Running cost of the four packaging scripts on the server: nothing beyond the box. The expensive part was never compute, it was getting the rules right.

What this turned into

The four scripts here are the packaging layer. In the full system they sit under an orchestrator, a scheduling agent, an LLM router, a prompt engine that generates the art, and a review pass that catches the rejects before the marketplace does. That system is what produced 1,700 assets on a single core.

I packaged the working tools, the scripts and the folder layout and the operational notes above, into one kit. It is the same code, not a cleaned up teaching version. If you are building the same thing, the metadata headers and the tracker are the two pieces that save you a rejected batch and a flagged account, and those are the parts worth starting with.

Get digital asset factory


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)