{
    "type": "article",
    "title": "A PDF service that only runs on your machine",
    "canonical": "https://www.dealmobile.co.uk/writing/a-pdf-service-that-only-runs-on-your-machine",
    "path": "/writing/a-pdf-service-that-only-runs-on-your-machine",
    "url": "https://www.dealmobile.co.uk/writing/a-pdf-service-that-only-runs-on-your-machine",
    "markdown": "https://www.dealmobile.co.uk/writing/a-pdf-service-that-only-runs-on-your-machine.md",
    "json": "https://www.dealmobile.co.uk/writing/a-pdf-service-that-only-runs-on-your-machine.json",
    "summary": "PrintMD’s PDF service turns Markdown into a PDF with one request, and it refuses to be hosted. Why that’s on purpose, and how it stays locked down even on your own computer.",
    "published": "2026-11-02T08:30:00+00:00",
    "modified": "2026-11-02T11:22:31+00:00",
    "image": {
        "url": "https://www.dealmobile.co.uk/sites/default/files/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hero.png",
        "alt": "Terminal output headed “Asked to serve the network, it will not start”. Started with PRINTMD_HOST=0.0.0.0, the service says it is refusing to start because it only binds a loopback address such as 127.0.0.1. Started with PRINTMD_AUTH=token, it says this edition’s PDF API runs only on your own computer and hosting it is not supported."
    },
    "fields": {
        "image_dark": null
    },
    "tags": [
        {
            "name": "Security",
            "url": "https://www.dealmobile.co.uk/writing/tag/security",
            "json": "https://www.dealmobile.co.uk/writing/tag/security.json"
        },
        {
            "name": "Markdown",
            "url": "https://www.dealmobile.co.uk/writing/tag/markdown",
            "json": "https://www.dealmobile.co.uk/writing/tag/markdown.json"
        },
        {
            "name": "PrintMD",
            "url": "https://www.dealmobile.co.uk/writing/tag/printmd",
            "json": "https://www.dealmobile.co.uk/writing/tag/printmd.json"
        }
    ],
    "projects": [
        {
            "name": "PrintMD",
            "url": "https://www.dealmobile.co.uk/projects/printmd",
            "json": "https://www.dealmobile.co.uk/projects/printmd.json"
        }
    ],
    "body": {
        "html": "<p><a href=\"/projects/printmd\">PrintMD</a> has a small PDF service. You send it Markdown and it sends back a PDF. I use it from scripts and from other tools, because one command is easier than opening an editor.</p>\n<p>You might expect me to say you can host it for your team. You can’t, and this post is about why that’s on purpose.</p>\n<h2 id=\"what-it-does\">What it does</h2>\n<p>The service drives a browser through Playwright and prints with it. The request is the Markdown and a few options: template, page size, layout, margin. The answer is the PDF, with headers that report the page count and any warnings about the document.</p>\n<figure class=\"shot shot--prose\"><img class=\"theme-img theme-img--light\" src=\"/sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers.png.webp?itok=Z2l1z2rc&amp;v=c041d6cc\" srcset=\"/sites/default/files/styles/almanac_shot_md/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers.png.webp?itok=ujHhVne_&amp;v=c041d6cc 1200w, /sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers.png.webp?itok=Z2l1z2rc&amp;v=c041d6cc 2400w\" sizes=\"(width &gt;= 39em) 37rem, calc(100vw - 2rem)\" alt=\"A terminal running curl against the PDF service’s render address with the Dealmobile template and notes.md. The response headers shown are HTTP/1.1 200 OK, Content-Type application/pdf, X-PrintMD-Pages 4 and X-PrintMD-Warnings %5B%5D.\" width=\"2400\" height=\"1120\" loading=\"lazy\" decoding=\"async\"><img class=\"theme-img theme-img--dark\" src=\"/sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers-dark.png.webp?itok=zVs09EvL&amp;v=19c5b31f\" srcset=\"/sites/default/files/styles/almanac_shot_md/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers-dark.png.webp?itok=2xdms8o5&amp;v=19c5b31f 1200w, /sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers-dark.png.webp?itok=zVs09EvL&amp;v=19c5b31f 2400w\" sizes=\"(width &gt;= 39em) 37rem, calc(100vw - 2rem)\" alt=\"A terminal running curl against the PDF service’s render address with the Dealmobile template and notes.md. The response headers shown are HTTP/1.1 200 OK, Content-Type application/pdf, X-PrintMD-Pages 4 and X-PrintMD-Warnings %5B%5D.\" width=\"2400\" height=\"1120\" loading=\"lazy\" decoding=\"async\"><figcaption>A real render. %5B%5D is an empty list, URL-encoded: no warnings. It listens on port 8787 by default; this one ran on 8791.</figcaption></figure>\n<p>By default that’s the Chromium Playwright downloads; set <code>PRINTMD_CHROME</code> and it uses your own Chrome instead. Left to itself, the browser stays open between renders. On Linux, the user service that <code>server/service/install-service.sh</code> installs closes it a minute after the last render, so between renders the service needs about as much memory as a small Node process. I haven’t put an exact number on that.</p>\n<h2 id=\"why-it-wont-be-hosted\">Why it won’t be hosted</h2>\n<p>A service that turns any text it’s sent into a rendered page, on a machine with a network, is a place where things go wrong. Someone sends a document that asks the browser to fetch an internal address, or to burn minutes of CPU, or to render a page the size of a billboard. Every one of those is something I’d have to defend against, forever, for a tool whose job is to make nice PDFs.</p>\n<p>On your own machine most of those questions go away, because the only person sending documents is you. So the service has one mode: local. It listens on 127.0.0.1, or another loopback address, and won’t start if you point it at anything else, or ask it to run behind a token or a login. That’s a decision, not a missing feature.</p>\n<h2 id=\"its-still-locked-down\">It’s still locked down</h2>\n<p>Local doesn’t mean careless. A Markdown file can contain images and links, and you may paste in something you didn’t write. So the render page is closed off:</p>\n<ul>\n<li>It can only load files that belong to the app. A request for anything else is blocked and listed in the response’s warnings header.</li>\n<li>The print page has a content security policy with scripts, frames and connections switched off.</li>\n<li>The Markdown can be up to 2 MB, not counting embedded images, a request up to 15 MB and one image up to 5 MB, and a render gets 20 seconds.</li>\n<li>It renders two documents at a time with a short queue, and answers “busy” to the rest.</li>\n</ul>\n<h2 id=\"the-editor\">The editor</h2>\n<p>The editor is a different case. It’s a static page, and nothing in it sends your documents anywhere, so hosting it is fine. When it’s hosted it says so:</p>\n<figure class=\"shot shot--prose\"><img class=\"theme-img theme-img--light\" src=\"/sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice.png.webp?itok=t7ZC-FWx&amp;v=393a22bf\" srcset=\"/sites/default/files/styles/almanac_shot_md/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice.png.webp?itok=pYzHQtE_&amp;v=393a22bf 1120w, /sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice.png.webp?itok=t7ZC-FWx&amp;v=393a22bf 1120w\" sizes=\"(width &gt;= 39em) 37rem, calc(100vw - 2rem)\" alt=\"The notice the hosted editor shows: This is a hosted copy. For the best results, run it on your own computer: the PDF service only works there, and it makes the best PDFs. A Dismiss button sits beside it.\" width=\"1120\" height=\"180\" loading=\"lazy\" decoding=\"async\"><img class=\"theme-img theme-img--dark\" src=\"/sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice-dark.png.webp?itok=5CxDfeJk&amp;v=60ed543d\" srcset=\"/sites/default/files/styles/almanac_shot_md/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice-dark.png.webp?itok=qjmdVgt6&amp;v=60ed543d 1120w, /sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice-dark.png.webp?itok=5CxDfeJk&amp;v=60ed543d 1120w\" sizes=\"(width &gt;= 39em) 37rem, calc(100vw - 2rem)\" alt=\"The notice the hosted editor shows: This is a hosted copy. For the best results, run it on your own computer: the PDF service only works there, and it makes the best PDFs. A Dismiss button sits beside it.\" width=\"1120\" height=\"180\" loading=\"lazy\" decoding=\"async\"><figcaption>The notice on the hosted editor, which can be dismissed.</figcaption></figure>\n<p>The editor makes its PDFs through the browser’s print dialog, hosted or not. The service is the other way to make one, and that only runs on your own computer.</p>\n<h2 id=\"what-this-costs\">What this costs</h2>\n<p>If you want a shared PDF service for a team, this is the wrong tool, and I don’t want to pretend otherwise. If you want good PDFs from your own notes, it’s a few commands and a folder.</p>"
    },
    "markdown_text": "---\ntitle: 'A PDF service that only runs on your machine'\ncanonical: 'https://www.dealmobile.co.uk/writing/a-pdf-service-that-only-runs-on-your-machine'\nurl: 'https://www.dealmobile.co.uk/writing/a-pdf-service-that-only-runs-on-your-machine'\ntype: Post\npublished: '2026-11-02'\nsummary: 'PrintMD’s PDF service turns Markdown into a PDF with one request, and it refuses to be hosted. Why that’s on purpose, and how it stays locked down even on your own computer.'\n---\n\n# A PDF service that only runs on your machine\n\nPublished 2026-11-02\n\n![Terminal output headed “Asked to serve the network, it will not start”. Started with PRINTMD_HOST=0.0.0.0, the service says it is refusing to start because it only binds a loopback address such as 127.0.0.1. Started with PRINTMD_AUTH=token, it says this edition’s PDF API runs only on your own computer and hosting it is not supported.](https://www.dealmobile.co.uk/sites/default/files/styles/almanac_shot_md/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hero.png.webp?itok=egbjWDlP)\n\n[PrintMD](https://www.dealmobile.co.uk/projects/printmd.md) has a small PDF service. You send it Markdown and it sends back a PDF. I use it from scripts and from other tools, because one command is easier than opening an editor.\n\nYou might expect me to say you can host it for your team. You can’t, and this post is about why that’s on purpose.\n\n## What it does\n\nThe service drives a browser through Playwright and prints with it. The request is the Markdown and a few options: template, page size, layout, margin. The answer is the PDF, with headers that report the page count and any warnings about the document.\n\n![A terminal running curl against the PDF service’s render address with the Dealmobile template and notes.md. The response headers shown are HTTP/1.1 200 OK, Content-Type application/pdf, X-PrintMD-Pages 4 and X-PrintMD-Warnings %5B%5D.](https://www.dealmobile.co.uk/sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/render-headers.png.webp?itok=Z2l1z2rc&v=c041d6cc)\n\n*A real render. %5B%5D is an empty list, URL-encoded: no warnings. It listens on port 8787 by default; this one ran on 8791.*\n\nBy default that’s the Chromium Playwright downloads; set `PRINTMD_CHROME` and it uses your own Chrome instead. Left to itself, the browser stays open between renders. On Linux, the user service that `server/service/install-service.sh` installs closes it a minute after the last render, so between renders the service needs about as much memory as a small Node process. I haven’t put an exact number on that.\n\n## Why it won’t be hosted\n\nA service that turns any text it’s sent into a rendered page, on a machine with a network, is a place where things go wrong. Someone sends a document that asks the browser to fetch an internal address, or to burn minutes of CPU, or to render a page the size of a billboard. Every one of those is something I’d have to defend against, forever, for a tool whose job is to make nice PDFs.\n\nOn your own machine most of those questions go away, because the only person sending documents is you. So the service has one mode: local. It listens on 127.0.0.1, or another loopback address, and won’t start if you point it at anything else, or ask it to run behind a token or a login. That’s a decision, not a missing feature.\n\n## It’s still locked down\n\nLocal doesn’t mean careless. A Markdown file can contain images and links, and you may paste in something you didn’t write. So the render page is closed off:\n\n- It can only load files that belong to the app. A request for anything else is blocked and listed in the response’s warnings header.\n- The print page has a content security policy with scripts, frames and connections switched off.\n- The Markdown can be up to 2 MB, not counting embedded images, a request up to 15 MB and one image up to 5 MB, and a render gets 20 seconds.\n- It renders two documents at a time with a short queue, and answers “busy” to the rest.\n\n## The editor\n\nThe editor is a different case. It’s a static page, and nothing in it sends your documents anywhere, so hosting it is fine. When it’s hosted it says so:\n\n![The notice the hosted editor shows: This is a hosted copy. For the best results, run it on your own computer: the PDF service only works there, and it makes the best PDFs. A Dismiss button sits beside it.](https://www.dealmobile.co.uk/sites/default/files/styles/almanac_shot/public/content/images/posts/a-pdf-service-that-only-runs-on-your-machine/hosted-notice.png.webp?itok=t7ZC-FWx&v=393a22bf)\n\n*The notice on the hosted editor, which can be dismissed.*\n\nThe editor makes its PDFs through the browser’s print dialog, hosted or not. The service is the other way to make one, and that only runs on your own computer.\n\n## What this costs\n\nIf you want a shared PDF service for a team, this is the wrong tool, and I don’t want to pretend otherwise. If you want good PDFs from your own notes, it’s a few commands and a folder.\n\n- **Projects:** [PrintMD](https://www.dealmobile.co.uk/projects/printmd.md)\n- **Tags:** [Security](https://www.dealmobile.co.uk/writing/tag/security.md), [Markdown](https://www.dealmobile.co.uk/writing/tag/markdown.md), [PrintMD](https://www.dealmobile.co.uk/writing/tag/printmd.md)\n\n---\n\nIndex of this site: [llms.txt](https://www.dealmobile.co.uk/llms.txt)\n"
}