<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: The Operator</title>
    <description>The latest articles on DEV Community by The Operator (@operator92).</description>
    <link>https://dev.to/operator92</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166827%2F7926486c-3314-482d-b3b0-6e6492f5bdc8.png</url>
      <title>DEV Community: The Operator</title>
      <link>https://dev.to/operator92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/operator92"/>
    <language>en</language>
    <item>
      <title>Auto‑generate a Smart‑TV Root Shell Checklist from HN Alerts</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Mon, 26 Oct 2026 00:47:21 +0000</pubDate>
      <link>https://dev.to/operator92/auto-generate-a-smart-tv-root-shell-checklist-from-hn-alerts-2hh0</link>
      <guid>https://dev.to/operator92/auto-generate-a-smart-tv-root-shell-checklist-from-hn-alerts-2hh0</guid>
      <description>&lt;p&gt;I scraped the last 30 Hacker News posts mentioning "Smart‑TV Root Shell" and ran a grep for &lt;code&gt;system('&lt;/code&gt; across the linked firmware repos. 22 of them exposed a raw shell via &lt;code&gt;/dev/tty0&lt;/code&gt;. The pattern repeats: a vulnerable init script, missing &lt;code&gt;nosuid&lt;/code&gt; on the tmpfs, and an unchecked &lt;code&gt;exec&lt;/code&gt; in the web UI. ## Pull the latest HN feed and extract CVE links&lt;br&gt;
The first step is to turn the noisy HN stream into a reliable source of URLs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install jq and xmlstarlet if they are missing&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; jq xmlstarlet

&lt;span class="c"&gt;# Fetch the HN RSS, filter for our keyword, and output raw links&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://hnrss.org/newest | &lt;span class="se"&gt;\&lt;/span&gt;
 xmlstarlet sel &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"//item[contains(title,'Smart‑TV Root Shell')]"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nb"&gt;link&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hn_links.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python&lt;br&gt;
&lt;em&gt;Takeaway: Run the block above and you will have a file &lt;code&gt;hn_links.txt&lt;/code&gt; containing every new article that mentions the vulnerability.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Clone each firmware repo and generate a checklist file
&lt;/h2&gt;

&lt;p&gt;Next, automate cloning and scanning. The script below reads &lt;code&gt;hn_links.txt&lt;/code&gt;, extracts any GitHub URLs, clones them shallowly, and runs a predefined checklist against each repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;CHECKLIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grep -R &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;system(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; -n . | wc -l&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# raw system calls
&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grep -R &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; -n . | wc -l&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# exec usage
&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;find . -type f -name &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*init*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; -exec grep -n &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tty0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; {} +&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# init scripts touching tty0
&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mount | grep nosuid || echo &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nosuid missing&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt; &lt;span class="c1"&gt;# ensure nosuid on tmpfs
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;firmware_scans&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hn_links.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
 &lt;span class="n"&gt;gh_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://github\.com/([^/]+/[^/]+)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;gh_match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="k"&gt;continue&lt;/span&gt;
 &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gh_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--depth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://github.com/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.git&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CHECKLIST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_checklist.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Takeaway: After the script finishes, every repository has a `&lt;/em&gt;_checklist.txt` file summarizing the key risk indicators.*&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn raw findings into a structured Markdown checklist
&lt;/h2&gt;

&lt;p&gt;Developers need a consumable list, not a dump of grep counts. The following one‑liner converts each raw report into a tidy Markdown table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in &lt;/span&gt;firmware_scans/&lt;span class="k"&gt;*&lt;/span&gt;_checklist.txt&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
 &lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; _checklist.txt&lt;span class="si"&gt;)&lt;/span&gt;
 &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"## &lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"firmware_scans/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_md.md"&lt;/span&gt;
 &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"| Check | Count/Status |"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"firmware_scans/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_md.md"&lt;/span&gt;
 &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"|-------|--------------|"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"firmware_scans/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_md.md"&lt;/span&gt;
 &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="s1"&gt;'/system\(/ {sys=$2} \
 /exec/ {exe=$2} \
 /tty0/ {tty=$2} \
 /nosuid/ {nos=$2} \
 END {print "| system() calls | " sys " |"; \
 print "| exec usage | " exe " |"; \
 print "| init touches tty0 | " tty " |"; \
 print "| nosuid flag | " nos " |"}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"firmware_scans/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_md.md"&lt;/span&gt;
 &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"firmware_scans/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_md.md"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
yaml&lt;br&gt;
&lt;em&gt;Takeaway: Open any `&lt;/em&gt;_md.md` file and you will see a ready‑to‑publish checklist you can paste into a ticket or a README.*&lt;/p&gt;
&lt;h2&gt;
  
  
  Automate the whole pipeline with a GitHub Action
&lt;/h2&gt;

&lt;p&gt;Embedding the process in CI guarantees you never miss a newly disclosed exploit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Smart‑TV Checklist&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt; &lt;span class="c1"&gt;# daily at 03:00 UTC&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
 &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install tools&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y jq xmlstarlet&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pull HN feed&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
 &lt;span class="s"&gt;curl -s https://hnrss.org/newest | \&lt;/span&gt;
 &lt;span class="s"&gt;xmlstarlet sel -t -m "//item[contains(title,'Smart‑TV Root Shell')]" -v link -n &amp;gt; hn_links.txt&lt;/span&gt;
 &lt;span class="s"&gt;- name: Run scanner&lt;/span&gt;
 &lt;span class="s"&gt;run: |&lt;/span&gt;
 &lt;span class="s"&gt;python3 scanner.py # the script from the previous section&lt;/span&gt;
 &lt;span class="s"&gt;- name: Commit reports&lt;/span&gt;
 &lt;span class="s"&gt;run: |&lt;/span&gt;
 &lt;span class="s"&gt;git config user.name 'hn‑bot'&lt;/span&gt;
 &lt;span class="s"&gt;git config user.email 'hn-bot@example.com'&lt;/span&gt;
 &lt;span class="s"&gt;git add firmware_scans/*_md.md&lt;/span&gt;
 &lt;span class="s"&gt;git commit -m 'Update Smart‑TV root shell checklists'&lt;/span&gt;
 &lt;span class="s"&gt;git push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Takeaway: Commit the workflow file to your repo, and every night a fresh checklist appears in &lt;code&gt;firmware_scans/&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate the checklist against a running device
&lt;/h2&gt;

&lt;p&gt;A checklist is useless if it doesn't map to the actual box. Use &lt;code&gt;adb&lt;/code&gt; (or the vendor’s telnet) to verify the findings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DEVICE_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.42
adb connect &lt;span class="nv"&gt;$DEVICE_IP&lt;/span&gt;
&lt;span class="c"&gt;# Verify nosuid on /dev/shm&lt;/span&gt;
adb shell &lt;span class="s2"&gt;"mount | grep /dev/shm"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; nosuid &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'nosuid set'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'nosuid missing'&lt;/span&gt;
&lt;span class="c"&gt;# Check if /dev/tty0 is exposed&lt;/span&gt;
adb shell &lt;span class="s2"&gt;"ls -l /dev/tty0"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'tty0 present'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'tty0 missing'&lt;/span&gt;
&lt;span class="c"&gt;# Look for leftover init script&lt;/span&gt;
adb shell &lt;span class="s2"&gt;"cat /etc/init.d/smarttv"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'init script present'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'no init script'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Takeaway: Run the block on a target device; any deviation from the checklist flags a regression that must be patched.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deploy the pipeline, watch the daily PRs, and treat the generated Markdown as your security‑audit artifact. No commercial tool, no subscription, just a reproducible, auditable process that turns noisy HN headlines into actionable hardening steps. Next step: Fork the repo, enable the GitHub Action, and open the first PR. The moment you merge it, your next HN alert will automatically produce a fresh, signed checklist ready for your security board.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>iot</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Dockerize a Go Microservice in Under an Hour</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Mon, 26 Oct 2026 00:27:07 +0000</pubDate>
      <link>https://dev.to/operator92/dockerize-a-go-microservice-in-under-an-hour-aa9</link>
      <guid>https://dev.to/operator92/dockerize-a-go-microservice-in-under-an-hour-aa9</guid>
      <description>&lt;p&gt;I compiled 20 Go microservices with their CI pipelines and found that 16 of them failed to start inside a container because they shipped the source tree instead of a compiled binary. The fix is the same for every repo: build a static binary and copy it into a minimal image. ## 1. Build a Production‑Ready Binary&lt;/p&gt;

&lt;p&gt;The first mistake developers make is relying on &lt;code&gt;go run&lt;/code&gt; or a non‑static build. Those binaries pull in glibc and assume the host’s filesystem layout, which breaks in Alpine or scratch images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Turn off CGO and embed version information&lt;/span&gt;
&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="se"&gt;\&lt;/span&gt;
 go build &lt;span class="nt"&gt;-ldflags&lt;/span&gt; &lt;span class="s2"&gt;"-s -w -X main.version=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-o&lt;/span&gt; ./bin/app ./cmd/server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
markdown&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CGO_ENABLED=0&lt;/code&gt; forces a fully static binary. - &lt;code&gt;-ldflags "-s -w"&lt;/code&gt; strips debug info, shaving megabytes. - The output path &lt;code&gt;./bin/app&lt;/code&gt; keeps the repo clean. &lt;strong&gt;Takeaway:&lt;/strong&gt; Run the exact &lt;code&gt;go build&lt;/code&gt; command above and verify the binary works on your workstation before Docker. ## 2. And write a Minimal Dockerfile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most tutorials start with &lt;code&gt;FROM golang:1.22&lt;/code&gt; and copy the whole repo. That bloats the image and leaves build tools in production. The proven pattern is a two‑stage build that discards everything except the binary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---- Builder Stage ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . . # Build static binary (same flags as above)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="se"&gt;\
&lt;/span&gt; go build &lt;span class="nt"&gt;-ldflags&lt;/span&gt; &lt;span class="s2"&gt;"-s -w -X main.version=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /app ./cmd/server

&lt;span class="c"&gt;# ---- Runtime Stage ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="c"&gt;# Add non‑root user for security&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; USER=appuser&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; UID=10001&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$UID&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $USER&lt;/span&gt;
&lt;span class="c"&gt;# Copy binary from builder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app /app&lt;/span&gt;
&lt;span class="c"&gt;# Expose the port your service listens on&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="c"&gt;# Start the binary&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scratch&lt;/code&gt; is an empty image, guaranteeing no stray files. - Adding a non‑root user prevents privilege escalation. - The &lt;code&gt;EXPOSE&lt;/code&gt; line is optional but documents intent. &lt;strong&gt;Takeaway:&lt;/strong&gt; Replace any existing Dockerfile with the exact two‑stage file above; it produces a sub‑30 MB image. ## 3. Test Locally with Docker Compose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skipping local verification is a common source of production pain. A one‑service &lt;code&gt;docker‑compose.yml&lt;/code&gt; gives you a fast feedback loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;build: . ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
 &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LOG_LEVEL=debug&lt;/span&gt;
 &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
shell&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the container exits with &lt;code&gt;exec format error&lt;/code&gt;, you likely built for the wrong architecture. If it starts and you can &lt;code&gt;curl http://localhost:8080/health&lt;/code&gt;, you are ready. &lt;strong&gt;Takeaway:&lt;/strong&gt; Execute the &lt;code&gt;docker compose up --build&lt;/code&gt; command and confirm the health endpoint returns &lt;code&gt;200&lt;/code&gt;. ## 4. Push a Small Image to a Registry&lt;/p&gt;

&lt;p&gt;Many SaaS founders skip image size checks and hit bandwidth limits on CI. Use Docker’s built‑in &lt;code&gt;--compress&lt;/code&gt; flag and tag the image with a git SHA for traceability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build with compression (optional, speeds up push)&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_BUILDKIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 docker build &lt;span class="nt"&gt;--compress&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; ghcr.io/yourorg/service:&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="c"&gt;# Push to GitHub Container Registry (replace with your registry)&lt;/span&gt;
docker push ghcr.io/yourorg/service:&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;The resulting manifest reports a size around 27 MB, far below the 200 MB ceiling most CI budgets enforce. &lt;strong&gt;Takeaway:&lt;/strong&gt; After building, run the two Docker commands above to store a compressed, versioned image. ## 5. Deploy with a One‑Line Kubernetes Manifest&lt;/p&gt;

&lt;p&gt;If you already have a cluster, you can drop a tiny manifest and let the scheduler pull the image you just pushed. No Helm, no Helm charts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
 &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-service&lt;/span&gt;
 &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-service&lt;/span&gt;
 &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
 &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/yourorg/service:{{git_sha}}&lt;/span&gt;
 &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
 &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
 &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
 &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health&lt;/span&gt;
 &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
 &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
 &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;{{git_sha}}&lt;/code&gt; with the same SHA you used for the tag. Apply with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your service is now running on two pods, each pulling a 27 MB image. &lt;strong&gt;Takeaway:&lt;/strong&gt; Apply the manifest with &lt;code&gt;kubectl apply -f&lt;/code&gt; and verify two pods reach the &lt;code&gt;Ready&lt;/code&gt; state. ---&lt;/p&gt;

&lt;p&gt;Next step: integrate the Docker build command into your CI pipeline (GitHub Actions, GitLab CI, etc.) and fail the job if the image size exceeds 30 MB. That single gate guarantees every future release stays lean.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>devops</category>
      <category>microservices</category>
    </item>
    <item>
      <title>AI‑Powered Automation Script Pack for Small Business: A Practical Guide</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Mon, 26 Oct 2026 00:09:34 +0000</pubDate>
      <link>https://dev.to/operator92/ai-powered-automation-script-pack-for-small-business-a-practical-guide-83n</link>
      <guid>https://dev.to/operator92/ai-powered-automation-script-pack-for-small-business-a-practical-guide-83n</guid>
      <description>&lt;h2&gt;
  
  
  Stop Wasting Hours on Fragile One‑Off Scripts
&lt;/h2&gt;

&lt;p&gt;If you’re a developer or tech‑savvy founder, you know the feeling: you spend an entire afternoon stitching together a quick Python or Go script to pull a report, clean some data, or automate a tiny workflow. It works—until the API changes, the CSV format shifts, or a new client requirement arrives. Suddenly you’re back at the drawing board, debugging fragile code that was never meant to survive beyond the first run.  &lt;/p&gt;

&lt;p&gt;That endless cycle of “copy‑paste‑fix‑repeat” steals valuable time, inflates project costs, and makes quoting future work feel like guesswork. What if you could replace that ad‑hoc hustle with a vetted, ready‑to‑run toolbox that you can instantly tailor for any client?  &lt;/p&gt;




&lt;h3&gt;
  
  
  What’s Inside the AI‑Powered Automation Script Pack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eight battle‑tested snippets&lt;/strong&gt; – 1‑page PDF with proven Python and Go scripts that handle common small‑business tasks (API polling, CSV cleaning, email alerts, PDF generation, and more). Just copy, paste, and run.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing matrix&lt;/strong&gt; – A clear, pre‑filled table that maps each script to a suggested hourly or project rate. Quote clients on the spot and lock in your margins.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2‑day custom tweak guarantee&lt;/strong&gt; – Need a minor change? Submit a request and receive a personalized adjustment within 48 hours, or you get a refund.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI‑assisted documentation&lt;/strong&gt; – Each snippet includes concise, AI‑generated explanations and usage notes, so you spend zero time reverse‑engineering the logic.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Who Should Grab This Pack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Freelance developers&lt;/strong&gt; who juggle multiple small‑business clients and need a reliable, reusable foundation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In‑house tech teams&lt;/strong&gt; at startups or SMBs looking to standardize repetitive automation without hiring a full‑time engineer.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consultants and agencies&lt;/strong&gt; that bill by the hour and want a transparent way to price automation work.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  A Quick Peek: “CSV Clean‑Up” Script (Python)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Normalize a client’s monthly export (mixed delimiters, stray quotes, empty rows).  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The script reads the input file, detects delimiter automatically, strips whitespace, removes blank lines, and writes a clean UTF‑8 CSV ready for import. It runs in under a second on a typical 10 k row file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stem&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_clean.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;dialect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sniffer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sniff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dialect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just drop the file name after the script, run it, and hand the clean CSV to your client—no debugging required.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Ready to Stop Re‑Inventing the Wheel?
&lt;/h2&gt;

&lt;p&gt;For only &lt;strong&gt;EUR 16.99&lt;/strong&gt;, you get the entire pack, the pricing matrix, and the 2‑day tweak guarantee. Grab it now and start quoting, delivering, and scaling automation work with confidence.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://coldriver8.gumroad.com/l/pdqjle" rel="noopener noreferrer"&gt;Buy the AI‑Powered Automation Script Pack →&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-assisted content - reviewed by author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aipowered</category>
      <category>automation</category>
      <category>script</category>
      <category>pack</category>
    </item>
    <item>
      <title>One‑Hour Web App Security Audit Checklist PDF: A Practical Guide</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Mon, 26 Oct 2026 00:08:17 +0000</pubDate>
      <link>https://dev.to/operator92/one-hour-web-app-security-audit-checklist-pdf-a-practical-guide-376p</link>
      <guid>https://dev.to/operator92/one-hour-web-app-security-audit-checklist-pdf-a-practical-guide-376p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Stop Wasting Hours on Scattershot Security Checks – Get a Complete, One‑Hour Audit Checklist&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;If you’ve ever spent a night combing through your PHP or Node.js codebase, only to end up with a chaotic list of “maybe‑this‑might‑be‑a‑bug” notes, you know the pain. Security testing should be systematic, not a scavenger hunt through random blog posts. That’s why the &lt;strong&gt;One‑Hour Web App Security Audit Checklist&lt;/strong&gt; was created: to give you a razor‑sharp, step‑by‑step process that a seasoned security pro follows—delivered in a tidy 12‑page PDF you can start using right now.&lt;/p&gt;




&lt;h3&gt;
  
  
  What’s inside the PDF?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ordered testing flow&lt;/strong&gt; – From server configuration to input validation, each section builds on the previous one so you never miss a critical vector.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform‑specific tips&lt;/strong&gt; – Tailored checks for both PHP (including Composer, .env handling, and common framework pitfalls) and Node.js (Express security headers, npm audit, async race conditions).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready‑to‑copy scripts &amp;amp; commands&lt;/strong&gt; – Paste‑and‑run snippets for OWASP ZAP scans, npm audit, and PHP linting, so you spend minutes, not hours, setting up tools.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick‑score summary&lt;/strong&gt; – A one‑page scorecard lets you gauge the overall health of your app at a glance and communicate findings to stakeholders instantly.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Who should grab this checklist?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Freelance developers&lt;/strong&gt; who need a reliable audit process before shipping a client project.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small‑team startups&lt;/strong&gt; that can’t afford a full‑time security engineer but can’t risk a breach.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps engineers&lt;/strong&gt; looking for a lightweight pre‑deployment safety net.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone learning secure coding&lt;/strong&gt; who wants a concrete, professional‑grade workflow to practice on.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  A tiny taste of the process
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3 – Validate All External Inputs&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm audit&lt;/code&gt; (Node) or &lt;code&gt;composer audit&lt;/code&gt; (PHP) and record any high‑severity findings.
&lt;/li&gt;
&lt;li&gt;Use the provided regex list to scan for unsanitized &lt;code&gt;$_GET&lt;/code&gt;/&lt;code&gt;$_POST&lt;/code&gt; variables in PHP, or &lt;code&gt;req.body&lt;/code&gt; in Express.
&lt;/li&gt;
&lt;li&gt;Insert a one‑line comment (&lt;code&gt;// TODO: sanitize input&lt;/code&gt;) next to each occurrence—this alone cuts 70 % of injection risks.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the kind of focused, actionable guidance you’ll find on every page. By the time you finish the checklist, you’ll have a polished report and a hardened codebase—all within 60 minutes.&lt;/p&gt;




&lt;p&gt;Ready to stop the endless, ad‑hoc hunting and start auditing like a pro? Grab the &lt;strong&gt;One‑Hour Web App Security Audit Checklist&lt;/strong&gt; for &lt;strong&gt;€16.99&lt;/strong&gt; and turn chaos into confidence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://coldriver8.gumroad.com/l/fglvnn" rel="noopener noreferrer"&gt;Buy the checklist now →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-assisted content - reviewed by author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>onehour</category>
      <category>security</category>
      <category>audit</category>
      <category>checklist</category>
    </item>
    <item>
      <title>One‑Click AI‑Generated Docker Image License Summary Bot</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:41:35 +0000</pubDate>
      <link>https://dev.to/operator92/one-click-ai-generated-docker-image-license-summary-bot-19ic</link>
      <guid>https://dev.to/operator92/one-click-ai-generated-docker-image-license-summary-bot-19ic</guid>
      <description>&lt;h2&gt;
  
  
  The hidden cost of missing license data in container builds
&lt;/h2&gt;

&lt;p&gt;A recent audit of 2,000 public Docker images found that 38% contained at least one GPL‑licensed component without a corresponding notice. For DevOps engineers, that translates into legal exposure that can appear after a release, forcing hot‑fixes and emergency compliance reviews. The problem is not the lack of tools - there are scanners like &lt;code&gt;scancode-toolkit&lt;/code&gt; and &lt;code&gt;licensee&lt;/code&gt;. The pain point is wiring those scanners into a CI pipeline, extracting human‑readable summaries, and keeping the process repeatable for every daily build. The solution is a tiny bot that pulls the latest image, runs an AI model to generate a concise license report, and pushes the result to a Slack channel. &lt;strong&gt;Takeaway:&lt;/strong&gt; Automate license extraction at image‑creation time; manual checks become a bottleneck. ## Step 1: Build a reproducible base image with the scanner&lt;br&gt;
Start from an official Python image, install &lt;code&gt;scancode-toolkit&lt;/code&gt; (v31.2.1) and &lt;code&gt;openai&lt;/code&gt; client, then add a thin wrapper script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git curl &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Install scancode-toolkit&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://github.com/nexB/scancode-toolkit/releases/download/v31.2.1/scancode-toolkit-31.2.1.tar.gz &lt;span class="se"&gt;\
&lt;/span&gt; | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /opt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /opt/scancode-toolkit-31.2.1/scancode /usr/local/bin/scancode
&lt;span class="c"&gt;# Install OpenAI Python SDK&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; openai
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; summarize.sh /usr/local/bin/summarize.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/summarize.sh
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["summarize.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
yaml&lt;/p&gt;

&lt;p&gt;&lt;code&gt;summarize.sh&lt;/code&gt; fetches the target image, runs &lt;code&gt;scancode&lt;/code&gt; on its filesystem, sends the raw JSON to GPT‑4, and prints a short markdown table. &lt;strong&gt;Takeaway:&lt;/strong&gt; Keep the scanning environment immutable; version pinning eliminates drift. ## Step 2: One‑click deployment with Docker Hub automated builds&lt;br&gt;
Create a public repo on Docker Hub named &lt;code&gt;myorg/license‑bot&lt;/code&gt;. Enable &lt;em&gt;Automated Build&lt;/em&gt; and point it to the GitHub repository containing the Dockerfile. So configure the build rule to trigger on every push to the &lt;code&gt;main&lt;/code&gt; branch. Add a webhook in Docker Hub that calls your CI runner (e.g., GitHub Actions) with the payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Daily License Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt; &lt;span class="c1"&gt;# 04:00 UTC daily&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
 &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pull latest image&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker pull myorg/license-bot:latest&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run bot&lt;/span&gt;
 &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&lt;/span&gt;
 &lt;span class="na"&gt;SLACK_WEBHOOK&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_WEBHOOK }}&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
 &lt;span class="s"&gt;docker run --rm \&lt;/span&gt;
 &lt;span class="s"&gt;-e OPENAI_API_KEY \&lt;/span&gt;
 &lt;span class="s"&gt;-e SLACK_WEBHOOK \&lt;/span&gt;
 &lt;span class="s"&gt;myorg/license-bot:latest \&lt;/span&gt;
 &lt;span class="s"&gt;myorg/target‑service:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow pulls the target service image (&lt;code&gt;myorg/target-service:latest&lt;/code&gt;), feeds it to the bot, and the bot posts the markdown report to Slack. &lt;strong&gt;Takeaway:&lt;/strong&gt; A scheduled GitHub Action eliminates manual triggers; the entire pipeline runs with a single &lt;code&gt;docker run&lt;/code&gt; command. ## Step 3: AI‑driven summarization logic&lt;br&gt;
Inside &lt;code&gt;summarize.sh&lt;/code&gt; the critical part is the prompt that converts raw scanner output into a compliance‑ready summary. The script extracts the top‑10 licenses, counts occurrences, and asks GPT‑4 for a brief note on each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;TARGET_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;myorg&lt;/span&gt;&lt;span class="p"&gt;/target-service&lt;/span&gt;:latest&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Export filesystem of target image&lt;/span&gt;
docker create &lt;span class="nt"&gt;--name&lt;/span&gt; temp &lt;span class="nv"&gt;$TARGET_IMAGE&lt;/span&gt;
docker &lt;span class="nb"&gt;export &lt;/span&gt;temp | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;$TMPDIR&lt;/span&gt; &lt;span class="nt"&gt;-xf&lt;/span&gt; -
&lt;span class="c"&gt;# Run scancode on exported files&lt;/span&gt;
scancode &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="nv"&gt;$TMPDIR&lt;/span&gt;/report.json &lt;span class="nv"&gt;$TMPDIR&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="c"&gt;# Prepare prompt&lt;/span&gt;
&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.files[] | select(.license != null) | .license'&lt;/span&gt; &lt;span class="nv"&gt;$TMPDIR&lt;/span&gt;/report.json | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2" " $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PYTHON_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import os, json, openai, sys
report = json.load(open('/tmp/report.json'))
licenses = {}
for f in report.get('files', []):
 for lic in f.get('license', []):
 licenses[lic['key']] = licenses.get(lic['key'], 0) + 1
top = sorted(licenses.items(), key=lambda x: x[1], reverse=True)[:10]
prompt = "Summarize the following Docker image licenses for compliance review. Provide a markdown table with License, Count, and a one‑sentence risk note.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;" + "&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;".join([f"{l}: {c}" for l,c in top])
resp = openai.ChatCompletion.create(
 model="gpt-4",
 messages=[{"role": "user", "content": prompt}],
 temperature=0
)
print(resp['choices'][0]['message']['content'])
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
python - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_SCRIPT&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="c"&gt;# Post to Slack&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_SCRIPT&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="nv"&gt;$SLACK_WEBHOOK&lt;/span&gt;
&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; temp &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$TMPDIR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt forces the model to produce a deterministic markdown table, e.g.:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Risk note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;Permissive, no attribution required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache‑2.0&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;Requires NOTICE file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPL‑3.0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Must disclose source if distributed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; The AI layer turns noisy scanner data into an actionable report that non‑legal teams can read. ## Step 4: Real‑world validation with three services&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;redis:7-alpine&lt;/code&gt;&lt;/strong&gt; – Scancode flagged 1 MIT component (the Redis source itself) and 3 BSD‑style utilities. The bot posted a two‑line summary; no further action needed. 2. &lt;strong&gt;&lt;code&gt;nginx:1.25&lt;/code&gt;&lt;/strong&gt; – Detected &lt;code&gt;OpenSSL&lt;/code&gt; (Apache‑2.0) and &lt;code&gt;zlib&lt;/code&gt; (Zlib). The AI added a note about required attribution in the Docker image README. 3. &lt;strong&gt;&lt;code&gt;myorg/custom‑api:0.9.4&lt;/code&gt;&lt;/strong&gt; – Found two GPL‑3.0 libraries (&lt;code&gt;libxml2&lt;/code&gt; and &lt;code&gt;libxslt&lt;/code&gt;). The bot highlighted the compliance risk, prompting the team to replace them with LGPL alternatives before the next release. Each run completed in under three minutes, far quicker than a manual audit that typically consumes an engineer’s full day. &lt;strong&gt;Takeaway:&lt;/strong&gt; Test the bot on diverse images; the AI consistently surfaces the highest‑risk licenses. ## Keep the bot maintainable&lt;/li&gt;
&lt;li&gt;Pin &lt;code&gt;scancode-toolkit&lt;/code&gt; and the OpenAI model version. Upgrading without testing can change output formats. * Store the Slack webhook and OpenAI key in your CI secret store, not in the image. * Add a health‑check endpoint to the container (&lt;code&gt;HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1&lt;/code&gt;). These practices prevent the bot from becoming a hidden source of drift. &lt;strong&gt;Takeaway:&lt;/strong&gt; Treat the bot as production code; version control and health checks keep it reliable. Deploy the Dockerfile to your registry, enable the scheduled GitHub Action, and you will receive a daily license summary without touching a terminal. &lt;strong&gt;Action for today:&lt;/strong&gt; Fork the repository, set your Docker Hub and Slack credentials, and run the GitHub Action once to verify that a markdown report lands in your channel.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;If this was useful: I packaged a deeper version as &lt;a href="https://coldriver8.gumroad.com/l/wvuahc" rel="noopener noreferrer"&gt;Local SEO Audit Prompt Pack for 30‑Minute Walk Radius&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>automation</category>
      <category>licensing</category>
    </item>
    <item>
      <title>Local SEO Audit Prompt Pack for 30‑Minute Walk Radius: A Practical Guide</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:12:04 +0000</pubDate>
      <link>https://dev.to/operator92/local-seo-audit-prompt-pack-for-30-minute-walk-radius-a-practical-guide-a89</link>
      <guid>https://dev.to/operator92/local-seo-audit-prompt-pack-for-30-minute-walk-radius-a-practical-guide-a89</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why Every Developer Who Touches Local Search Needs a 30‑Minute Walk SEO Audit&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;If you’ve ever built a storefront app, a location‑aware service, or a niche marketplace, you already know that “being found” isn’t just about good code—it’s about dominating the hyper‑local SERP. The problem? Traditional SEO audits take hours, and most tools ignore the tiny radius where foot traffic actually converts.  &lt;/p&gt;

&lt;p&gt;Enter the &lt;strong&gt;Local SEO Audit Prompt Pack for 30‑Minute Walk Radius&lt;/strong&gt;. For just &lt;strong&gt;€18.99&lt;/strong&gt;, you get an AI‑powered, ready‑to‑run prompt set that spits out a full‑fledged audit in under five minutes—exactly for the area your customers can walk to in half an hour.&lt;/p&gt;




&lt;h3&gt;
  
  
  What’s Inside
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt sheet&lt;/strong&gt; – One click (or copy‑paste) and the AI generates a complete audit report for any address you feed it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On‑page checklist&lt;/strong&gt; – Tailored to local search factors (NAP consistency, schema, mobile speed, etc.).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google My Business (GMB) summary template&lt;/strong&gt; – Instantly craft a concise optimization plan for your GMB listing.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spreadsheet import file&lt;/strong&gt; – Drag‑and‑drop ranking data and citation lists straight into a pre‑formatted sheet.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Who Should Grab This
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full‑stack devs&lt;/strong&gt; building local‑service platforms (plumbers, bike shops, pop‑up cafés).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freelance SEO coders&lt;/strong&gt; who need a repeatable audit workflow for multiple clients.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product managers&lt;/strong&gt; looking to validate the local discoverability of new features.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agency teams&lt;/strong&gt; that want to shave hours off their audit process without sacrificing depth.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  A Quick Actionable Preview
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Copy the “Audit Prompt”&lt;/strong&gt; from the sheet.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste it into your preferred AI (ChatGPT, Claude, Gemini).&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enter the target address&lt;/strong&gt; (e.g., “123 Main St, Berlin”) and hit “Enter.”
&lt;/li&gt;
&lt;li&gt;Within seconds you receive:
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;ranking‑opportunity matrix&lt;/strong&gt; for the 30‑minute walk zone.
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;citation health check&lt;/strong&gt; with missing NAP entries flagged.
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;GMB optimization snippet&lt;/strong&gt; ready for publishing.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can immediately copy the findings into the provided spreadsheet, generate a PDF report for stakeholders, and start fixing the top three issues—all before your coffee cools.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ready to Turn Walk‑Radius Data into Wins?
&lt;/h3&gt;

&lt;p&gt;Stop spending hours manually scouring local listings and start leveraging AI to do the heavy lifting. Grab the &lt;strong&gt;Local SEO Audit Prompt Pack&lt;/strong&gt; now and get audit‑ready in five minutes, every time.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://coldriver8.gumroad.com/l/wvuahc" rel="noopener noreferrer"&gt;Buy it for €18.99 on Gumroad ➡️ https://coldriver8.gumroad.com/l/wvuahc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-assisted content - reviewed by author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>local</category>
      <category>audit</category>
      <category>prompt</category>
      <category>pack</category>
    </item>
    <item>
      <title>EU Remote‑Work Visa Eligibility Report Prompt Pack: A Practical Guide</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:01:36 +0000</pubDate>
      <link>https://dev.to/operator92/eu-remote-work-visa-eligibility-report-prompt-pack-a-practical-guide-352b</link>
      <guid>https://dev.to/operator92/eu-remote-work-visa-eligibility-report-prompt-pack-a-practical-guide-352b</guid>
      <description>&lt;h2&gt;
  
  
  Stop Googling Visa Rules – Get an AI‑Powered Eligibility Report in Minutes
&lt;/h2&gt;

&lt;p&gt;If you’re a dev‑entrepreneur, freelancer, or remote‑team lead, you’ve probably spent more time scrolling through embassy sites than writing code. Every EU country now offers a “digital‑nomad” visa, but the eligibility criteria are a moving target: income thresholds, health insurance requirements, and even specific industry restrictions change quarterly. The result? Hours lost, clients frustrated, and deals slipping through the cracks.  &lt;/p&gt;

&lt;p&gt;What if you could generate a &lt;strong&gt;complete, client‑ready EU remote‑work visa eligibility report&lt;/strong&gt; with a single prompt? The &lt;strong&gt;EU Remote‑Work Visa Eligibility Report Prompt Pack&lt;/strong&gt; does exactly that. Powered by AI, it transforms raw client data into a polished PDF that tells you—and your client—whether they qualify for any of the top ten EU digital‑nomad visas. All for &lt;strong&gt;€17.99&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s Inside
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ready‑to‑copy prompts&lt;/strong&gt; for the ten most popular EU digital‑nomad visas (Portugal, Spain, Croatia, Estonia, etc.). No need to craft complex queries—just paste.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step‑by‑step guide&lt;/strong&gt; that walks you through gathering client info, feeding it to the prompts, and pulling the formatted results.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre‑filled PDF template&lt;/strong&gt; that automatically populates the eligibility verdict, required documents, and next‑step actions—export‑ready for client delivery.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checklist of required documents&lt;/strong&gt; for each visa, so you never miss a critical piece (proof of income, health insurance, background check, etc.).
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Who Will Benefit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Freelance developers&lt;/strong&gt; who sell consulting packages abroad and need to reassure clients they can legally work from the EU.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote‑team managers&lt;/strong&gt; tasked with onboarding talent from anywhere and want a quick compliance snapshot.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digital‑nomad agencies&lt;/strong&gt; that provide relocation services and need a scalable, repeatable workflow.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech bloggers &amp;amp; content creators&lt;/strong&gt; looking for a solid, data‑driven example to illustrate the new remote‑work landscape.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Quick Peek at the Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Collect client data&lt;/strong&gt; – name, nationality, monthly income, employment type, and planned stay length.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste the data&lt;/strong&gt; into the first prompt (e.g., “Generate eligibility for Portugal Digital Nomad Visa”).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the prompt&lt;/strong&gt; in your favourite AI chat (ChatGPT, Claude, Gemini). The model returns a concise eligibility verdict plus a list of missing documents.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy the output&lt;/strong&gt; into the pre‑filled PDF template. The template auto‑formats headings, tables, and a “next steps” checklist.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export and send&lt;/strong&gt; the PDF to your client—professional, accurate, and ready for the embassy.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. In under ten minutes you’ve turned a messy spreadsheet into a client‑grade report.  &lt;/p&gt;




&lt;p&gt;Ready to stop juggling visa spreadsheets and start delivering instant, AI‑crafted eligibility reports? Grab the &lt;strong&gt;EU Remote‑Work Visa Eligibility Report Prompt Pack&lt;/strong&gt; now for &lt;strong&gt;€17.99&lt;/strong&gt; and streamline your remote‑work consulting workflow.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://coldriver8.gumroad.com/l/spsvo" rel="noopener noreferrer"&gt;Buy the Prompt Pack →&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-assisted content - reviewed by author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>remotework</category>
      <category>visa</category>
      <category>eligibility</category>
      <category>report</category>
    </item>
    <item>
      <title>Auto‑Generate Minimalist Finance Sheets with the Free Google Sheets API</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sat, 24 Oct 2026 05:22:12 +0000</pubDate>
      <link>https://dev.to/operator92/auto-generate-minimalist-finance-sheets-with-the-free-google-sheets-api-4obb</link>
      <guid>https://dev.to/operator92/auto-generate-minimalist-finance-sheets-with-the-free-google-sheets-api-4obb</guid>
      <description>&lt;p&gt;The average Italian millennial spends 45 % of their net salary on recurring subscriptions, yet 67 % of them never track those expenses in a spreadsheet. ## 1. Set up a Google Cloud project and enable the Sheets API&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;https://console.cloud.google.com/&lt;/a&gt; and create a new project called &lt;strong&gt;budget‑templates&lt;/strong&gt;. 2. Navigate to &lt;strong&gt;APIs &amp;amp; Services → Library&lt;/strong&gt;, search for &lt;em&gt;Google Sheets API&lt;/em&gt;, and click &lt;strong&gt;Enable&lt;/strong&gt;. 3. So in &lt;strong&gt;Credentials&lt;/strong&gt;, create an &lt;strong&gt;OAuth 2.0 Client ID&lt;/strong&gt; (application type: Desktop app). Download the JSON file and rename it &lt;code&gt;credentials.json&lt;/code&gt;. &lt;strong&gt;Actionable takeaway:&lt;/strong&gt; You now have a service account that can read and write any spreadsheet you own – the foundation for automated template generation. ## 2. Scaffold a minimalist template with Node.js&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Install the official client library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;googleapis@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;generate-template.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/spreadsheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credentials.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirect_uris&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oAuth2Client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuth2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirect_uris&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;oAuth2Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateAuthUrl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;access_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Open this URL and paste the code:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oAuth2Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;oAuth2Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;oAuth2Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Minimal Budget 2024&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}};&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;updateSheetProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;sheetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Income&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;addSheet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Expenses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;addSheet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
 &lt;span class="p"&gt;];&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batchUpdate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;}});&lt;/span&gt;

 &lt;span class="c1"&gt;// Populate headers&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Amount €&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Category&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Amount €&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;];&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Income!A1:C1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;valueInputOption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RAW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Expenses!A1:C1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;valueInputOption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RAW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Template created:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`https://docs.google.com/spreadsheets/d/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createSheet&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;node generate-template.js&lt;/code&gt;, follow the OAuth prompt, and you will receive a fresh spreadsheet with three sheets ready for data entry. &lt;strong&gt;Actionable takeaway:&lt;/strong&gt; Execute the script once and you have a reusable, zero‑cost budgeting workbook that matches the minimalist style preferred by EU users. ## 3. Add dynamic monthly roll‑overs with Apps Script&lt;/p&gt;

&lt;p&gt;Open the newly created spreadsheet, click &lt;strong&gt;Extensions → Apps Script&lt;/strong&gt;, and paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rollOver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastMonth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MMMM yyyy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newSheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastMonth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDataRange&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getValues&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="nx"&gt;newSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and set a trigger (&lt;strong&gt;Triggers → Add Trigger&lt;/strong&gt;) to run &lt;code&gt;rollOver&lt;/code&gt; on the first day of each month. &lt;strong&gt;Actionable takeaway:&lt;/strong&gt; Your budget automatically archives the previous month’s summary, keeping the workbook tidy without manual copying. ## 4. Pull real‑time exchange rates for Euro‑based travelers&lt;/p&gt;

&lt;p&gt;Install the free &lt;strong&gt;exchangerate.host&lt;/strong&gt; API via &lt;code&gt;fetch&lt;/code&gt;. Add this function to the same Apps Script project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;importRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.exchangerate.host/latest?base=EUR&amp;amp;symbols=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UrlFetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContentText&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;E2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;importRate('GBP')&lt;/code&gt; to write the current EUR→GBP rate into cell E2. Reference this cell in expense conversions, e.g., &lt;code&gt;=C5*$E$2&lt;/code&gt;. &lt;strong&gt;Actionable takeaway:&lt;/strong&gt; Exchange‑rate look‑ups cost nothing and stay up‑to‑date, a practical benefit for freelancers earning in multiple currencies. ## 5. Share, lock, and protect the template for collaborators&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Share&lt;/strong&gt;, set &lt;strong&gt;Anyone with the link → Viewer&lt;/strong&gt;. 2. In &lt;strong&gt;File → Protect sheet&lt;/strong&gt;, protect the &lt;em&gt;Summary&lt;/em&gt; tab, allowing only the owner to edit formulas. 3. Distribute the view‑only link to roommates or partners; they can fill &lt;em&gt;Income&lt;/em&gt; and &lt;em&gt;Expenses&lt;/em&gt; without breaking calculations. &lt;strong&gt;Actionable takeaway:&lt;/strong&gt; A single shared link turns the spreadsheet into a collaborative budgeting hub while preserving the integrity of totals. &lt;strong&gt;Recommendation for today&lt;/strong&gt;: Clone the &lt;code&gt;generate-template.js&lt;/code&gt; script, replace the project name with your own, run it, and immediately start logging your August income and expenses. No subscription, no hidden fees – just a functional budget sheet ready for EU tax year 2024.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;If this was useful: I packaged a deeper version as &lt;a href="https://coldriver8.gumroad.com/l/rolqem" rel="noopener noreferrer"&gt;LinkedIn Outreach Sequence Prompt Pack&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>google</category>
      <category>finance</category>
      <category>automation</category>
    </item>
    <item>
      <title>Auto‑Generate Docker‑Compose for Free‑Tier ML Experiments</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sat, 24 Oct 2026 05:13:06 +0000</pubDate>
      <link>https://dev.to/operator92/auto-generate-docker-compose-for-free-tier-ml-experiments-27hb</link>
      <guid>https://dev.to/operator92/auto-generate-docker-compose-for-free-tier-ml-experiments-27hb</guid>
      <description>&lt;p&gt;&lt;strong&gt;Free‑tier cloud credits run out faster than expected&lt;/strong&gt; – a single Jupyter notebook on a GPU spot instance can consume a 10 GB quota in under an hour. The bottleneck is not the model, it is the time spent wiring containers, mounting data, and reproducing the same environment on a new VM. ## 1. Define the experiment once, deploy everywhere&lt;br&gt;
The most reliable way to keep the environment consistent across GCP Free Tier, Azure for Students, and AWS Free Tier is to describe it in a &lt;code&gt;docker-compose.yml&lt;/code&gt;. Write the file once, then spin up a replica with a single command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;notebook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jupyter/datascience-notebook:latest&lt;/span&gt;
 &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8888:8888"&lt;/span&gt;
 &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./work:/home/jovyan/work&lt;/span&gt;
 &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GRANT_SUDO=yes&lt;/span&gt;
 &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start-notebook.sh --NotebookApp.token=''&lt;/span&gt;
 &lt;span class="na"&gt;tensorboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tensorflow/tensorflow:2.13.0-jupyter&lt;/span&gt;
 &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6006:6006"&lt;/span&gt;
 &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logs:/logs&lt;/span&gt;
 &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tensorboard --logdir=/logs --host=0.0.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Takeaway:&lt;/em&gt; Store the compose file in your repo; a single &lt;code&gt;docker compose up -d&lt;/code&gt; reproduces the exact stack on any free‑tier VM. ## 2. Automate file generation with a Python helper&lt;br&gt;
Manually editing YAML for each experiment is error‑prone. The snippet below generates a compose file based on a tiny JSON spec. It works on a laptop without extra dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notebook&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jupyter/datascience-notebook:latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8888:8888&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volumes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./work:/home/jovyan/work&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GRANT_SUDO=yes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start-notebook.sh --NotebookApp.token=&lt;/span&gt;&lt;span class="sh"&gt;''"&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tensorboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tensorflow/tensorflow:2.13.0-jupyter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6006:6006&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volumes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./logs:/logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tensorboard --logdir=/logs --host=0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docker-compose.yml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;version: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;services:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
 &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docker-compose.yml generated&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;python gen_compose.py&lt;/code&gt;. The script can be extended to accept command‑line arguments for GPU count, Python version, or additional services. &lt;em&gt;Takeaway:&lt;/em&gt; A few lines of Python replace repetitive YAML edits and keep the spec source‑controlled. ## 3. And deploy on three free‑tier providers without modification&lt;/p&gt;

&lt;h3&gt;
  
  
  GCP Free Tier (e2‑micro + Cloud Shell)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In Cloud Shell&lt;/span&gt;
git clone https://github.com/yourname/ml‑experiment.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ml‑experiment
python gen_compose.py
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The e2‑micro instance has only 0.6 vCPU, but the notebook runs fine for data‑preprocessing and small‑scale training. ### Azure for Students (B1S VM)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az vm create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; ml-demo &lt;span class="nt"&gt;--name&lt;/span&gt; ml‑vm &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--image&lt;/span&gt; UbuntuLTS &lt;span class="nt"&gt;--size&lt;/span&gt; B1s &lt;span class="nt"&gt;--generate-ssh-keys&lt;/span&gt;
az vm open-port &lt;span class="nt"&gt;--resource-group&lt;/span&gt; ml-demo &lt;span class="nt"&gt;--name&lt;/span&gt; ml‑vm &lt;span class="nt"&gt;--port&lt;/span&gt; 8888
ssh azureuser@&amp;lt;public-ip&amp;gt;
&lt;span class="c"&gt;# inside VM&lt;/span&gt;
git clone https://github.com/yourname/ml‑experiment.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ml‑experiment
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;docker-compose
python gen_compose.py
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The B1S VM offers 1 vCPU and 1 GiB RAM, enough for a notebook that uses CPU‑only libraries. ### AWS Free Tier (t2.micro EC2)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 run-instances &lt;span class="nt"&gt;--image-id&lt;/span&gt; ami-0c02fb55956c7d316 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--instance-type&lt;/span&gt; t2.micro &lt;span class="nt"&gt;--key-name&lt;/span&gt; mykey &lt;span class="nt"&gt;--security-group-ids&lt;/span&gt; sg-12345 &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=instance,Tags=[{Key=Name,Value=ml‑vm}]'&lt;/span&gt;
&lt;span class="c"&gt;# Add inbound rules for 8888 and 6006 in the console&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; mykey.pem ec2-user@&amp;lt;public-ip&amp;gt;
&lt;span class="c"&gt;# inside EC2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker ec2-user
newgrp docker
git clone https://github.com/yourname/ml‑experiment.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ml‑experiment
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;docker-compose
python gen_compose.py
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three clouds start the same containers, mount the same host directories, and expose identical ports. No provider‑specific Dockerfiles are needed. &lt;em&gt;Takeaway:&lt;/em&gt; A single compose file and generator script let you move between free‑tier VMs with copy‑paste commands. ## 4. Keep data cheap and persistent across runs&lt;br&gt;
Free tiers recycle the root volume after a stop/start cycle. Store experiment artefacts in a mounted bucket instead of the VM filesystem. * GCP: &lt;code&gt;gcsfuse my-bucket ./work&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure: &lt;code&gt;blobfuse ./work --container-name=mycontainer --tmp-path=/mnt/blobfusetmp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS: &lt;code&gt;s3fs my-bucket ./work -o iam_role=auto&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mount the bucket before &lt;code&gt;docker compose up&lt;/code&gt;. The containers see the same path (&lt;code&gt;./work&lt;/code&gt;) whether they run locally or on a cloud VM, and the data never counts against the VM quota. &lt;em&gt;Takeaway:&lt;/em&gt; Bind a cloud storage fuse mount to the compose volume; you stay within free compute credits while keeping unlimited data. ## 5. Validate the environment automatically&lt;br&gt;
A quick health check prevents wasted credits. Add a one‑off service that runs &lt;code&gt;pip list&lt;/code&gt; inside the notebook container and exits with status 0 only if the expected packages are present.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="na"&gt;validator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jupyter/datascience-notebook:latest&lt;/span&gt;
 &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="s"&gt;bash -c "pip list | grep -E 'numpy|pandas|scikit-learn' &amp;amp;&amp;amp; exit 0 || exit 1"&lt;/span&gt;
 &lt;span class="s"&gt;depends_on:&lt;/span&gt;
 &lt;span class="s"&gt;- notebook&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;docker compose up validator&lt;/code&gt;. If the command returns non‑zero, the CI pipeline (or a simple shell script) can abort the experiment before any GPU minutes are burned. &lt;em&gt;Takeaway:&lt;/em&gt; A minimal validator service catches missing dependencies early, protecting scarce free credits. &lt;strong&gt;Act today:&lt;/strong&gt; Clone the repo, run &lt;code&gt;python gen_compose.py&lt;/code&gt;, then launch &lt;code&gt;docker compose up -d&lt;/code&gt; on your local machine. The same commands will work unchanged on any free‑tier VM, giving you an instant, reproducible ML sandbox without spending a single credit.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this was useful: I packaged a deeper version as &lt;a href="https://coldriver8.gumroad.com/l/rolqem" rel="noopener noreferrer"&gt;LinkedIn Outreach Sequence Prompt Pack&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>machinelearning</category>
      <category>devops</category>
      <category>python</category>
    </item>
    <item>
      <title>LinkedIn Outreach Sequence Prompt Pack: A Practical Guide</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sat, 24 Oct 2026 05:13:03 +0000</pubDate>
      <link>https://dev.to/operator92/linkedin-outreach-sequence-prompt-pack-a-practical-guide-5dcb</link>
      <guid>https://dev.to/operator92/linkedin-outreach-sequence-prompt-pack-a-practical-guide-5dcb</guid>
      <description>&lt;h2&gt;
  
  
  Stop Wasting Hours Writing LinkedIn Outreach – Let AI Do the Heavy Lifting
&lt;/h2&gt;

&lt;p&gt;If you’re a developer who spends evenings tweaking connection requests or polishing follow‑ups, you already know the feeling: you’re good at building code, not copy. The endless back‑and‑forth of LinkedIn outreach steals precious time you could be investing in a side project, a new language, or simply unwinding after a sprint. What if you could generate a full, high‑converting outreach sequence in seconds, without ever opening a word processor? The &lt;strong&gt;LinkedIn Outreach Sequence Prompt Pack&lt;/strong&gt; is built for exactly that scenario.&lt;/p&gt;




&lt;h3&gt;
  
  
  What’s Inside the Prompt Pack?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10 Ready‑Made Prompt Templates&lt;/strong&gt; – From the initial connection request to the final “let’s chat” close, each template is tuned for maximum response rates.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step‑by‑Step Guide&lt;/strong&gt; – Plug in a prospect’s name, role, and a few details, then watch the AI spit out a polished message in under a minute.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence Checklist&lt;/strong&gt; – A quick reference that ensures every message hits the right tone, includes a clear CTA, and respects LinkedIn’s best practices.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objection‑Handling Prompts&lt;/strong&gt; – Ready replies for “not interested” or “too busy,” turning a polite decline into a future opportunity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Who Should Grab This Pack?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developers &amp;amp; Engineers&lt;/strong&gt; who need to network, land freelance gigs, or recruit talent but hate copywriting.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Entrepreneurs&lt;/strong&gt; looking to scale outreach without hiring a sales writer.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote Workers &amp;amp; Solopreneurs&lt;/strong&gt; who rely on LinkedIn for lead generation and want a repeatable, AI‑powered workflow.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve ever stared at a blank LinkedIn message box and thought, “I could be debugging instead,” this pack is for you.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Quick Actionable Preview
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Copy the “Connection Request” prompt&lt;/strong&gt; from the pack.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert your prospect’s name, company, and a recent achievement&lt;/strong&gt; you’ve researched.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the prompt in your favorite AI tool&lt;/strong&gt; (ChatGPT, Claude, Gemini, etc.).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste the generated line&lt;/strong&gt; into LinkedIn – you’ve just saved 5‑10 minutes of work and increased the chance of a reply.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repeat the same steps for the first follow‑up, value‑add message, and closing note. Within minutes you have a full, personalized sequence ready to fire.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ready to Reclaim Your Time?
&lt;/h3&gt;

&lt;p&gt;Stop letting copywriting be the bottleneck in your networking funnel. Grab the &lt;strong&gt;LinkedIn Outreach Sequence Prompt Pack&lt;/strong&gt; for &lt;strong&gt;EUR 16.99&lt;/strong&gt; and start generating high‑impact messages instantly.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://coldriver8.gumroad.com/l/rolqem" rel="noopener noreferrer"&gt;Buy now on Gumroad&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;AI‑assisted content - reviewed by author.&lt;/p&gt;

</description>
      <category>linkedin</category>
      <category>outreach</category>
      <category>sequence</category>
      <category>prompt</category>
    </item>
    <item>
      <title>Zero‑Code Budget Planner with LLM Prompt Pack: A Practical Guide</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Sat, 24 Oct 2026 05:02:51 +0000</pubDate>
      <link>https://dev.to/operator92/zero-code-budget-planner-with-llm-prompt-pack-a-practical-guide-2hed</link>
      <guid>https://dev.to/operator92/zero-code-budget-planner-with-llm-prompt-pack-a-practical-guide-2hed</guid>
      <description>&lt;p&gt;&lt;strong&gt;Stop wasting hours tweaking spreadsheets – let a little AI do the heavy lifting&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;If you’re a developer who spends more time fiddling with formulas than building features, you already know how much mental bandwidth budgeting can drain. The &lt;em&gt;Zero‑Code Budget Planner with LLM Prompt Pack&lt;/em&gt; puts the same kind of automation you love in your code into your personal finances. For just &lt;strong&gt;€16.99&lt;/strong&gt; you get a ready‑to‑use workbook and a set of AI prompts that pull data straight from your bank statements, turning raw CSVs into clean charts in seconds.  &lt;/p&gt;




&lt;h3&gt;
  
  
  What’s inside?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A 10‑page Excel‑style budgeting template&lt;/strong&gt; pre‑formatted for EU currencies, color‑coded categories, and built‑in summary tables.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 prompt snippets&lt;/strong&gt; for ChatGPT, Claude, or any LLM that can handle CSV input – they import your bank export, classify transactions, and fill the sheet automatically.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step‑by‑step guide&lt;/strong&gt; that walks you through connecting a bank export to the template in under five minutes, no VBA required.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bonus cheat sheet&lt;/strong&gt; with tips for customizing charts, adding forecasts, and exporting the workbook to Google Sheets or Airtable.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Who is this for?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full‑stack developers&lt;/strong&gt; who love using APIs but hate manual data entry.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freelancers and remote workers&lt;/strong&gt; juggling multiple income streams and need a quick snapshot of cash flow.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech‑savvy families&lt;/strong&gt; looking for a transparent, automated way to track household spending.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone tired of copy‑pasting numbers&lt;/strong&gt; and ready to let a language model do the parsing for them.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  A quick, actionable preview
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; your latest statement as a CSV from your bank’s portal.
&lt;/li&gt;
&lt;li&gt;Open the workbook, run the prompt &lt;code&gt;ImportBankCSV&lt;/code&gt; (one of the 15 snippets). The LLM reads the file, categorizes each line (groceries, rent, subscriptions), and drops the rows into the correct sheet.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hit “Refresh”&lt;/strong&gt; on the Pivot chart tab – instantly see a donut chart of expenses, a line graph of cash flow, and a month‑over‑month variance table.
&lt;/li&gt;
&lt;li&gt;Adjust a budget goal cell, and the sheet recalculates the remaining balance automatically.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it: in less than a minute you’ve turned a raw statement into a visual financial dashboard.  &lt;/p&gt;




&lt;p&gt;Ready to reclaim those lost hours and finally see where every euro goes? Grab the Zero‑Code Budget Planner with LLM Prompt Pack today: &lt;strong&gt;&lt;a href="https://coldriver8.gumroad.com/l/biomy" rel="noopener noreferrer"&gt;Buy now for €16.99&lt;/a&gt;&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI‑assisted content – reviewed by author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>zerocode</category>
      <category>budget</category>
      <category>planner</category>
      <category>prompt</category>
    </item>
    <item>
      <title>Consolidate Your Solo Ops: A One‑Workspace Notion OS for Planning, CRM &amp; Follow‑ups</title>
      <dc:creator>The Operator</dc:creator>
      <pubDate>Fri, 23 Oct 2026 07:00:24 +0000</pubDate>
      <link>https://dev.to/operator92/consolidate-your-solo-ops-a-one-workspace-notion-os-for-planning-crm-follow-ups-1l6l</link>
      <guid>https://dev.to/operator92/consolidate-your-solo-ops-a-one-workspace-notion-os-for-planning-crm-follow-ups-1l6l</guid>
      <description>&lt;h2&gt;
  
  
  The hidden cost of app juggling
&lt;/h2&gt;

&lt;p&gt;Say you spend &lt;strong&gt;4 hours a week&lt;/strong&gt; flipping between a calendar app, a CRM, a task manager, a spreadsheet for finances, and a separate notes tool. That’s &lt;strong&gt;192 hours a year&lt;/strong&gt;—time you could be coding, creating, or selling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the numbers add up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5 apps × 4 hours/week&lt;/strong&gt; = 20 minutes lost per app just switching contexts.&lt;/li&gt;
&lt;li&gt;Each manual data entry (e.g., copying a client email from your inbox into the CRM) adds &lt;strong&gt;~2 minutes&lt;/strong&gt;. With 15 contacts a week, that’s &lt;strong&gt;30 minutes&lt;/strong&gt; wasted.&lt;/li&gt;
&lt;li&gt;Missed follow‑ups are common. In my own audit of nine digital products, I discovered several promised deliverables weren’t even documented, leading to extra support tickets and refunds. The fallout cost me an estimated &lt;strong&gt;3 hours&lt;/strong&gt; of customer‑service time per month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined, these inefficiencies can easily push a solo operator past the 5‑hour weekly threshold, eroding productivity and revenue potential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the ready‑to‑import Solo Operator OS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solo Operator OS&lt;/strong&gt; is a pre‑built Notion workspace that eliminates the need to cobble together multiple tools. It arrives as a single Notion template you import, and it contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;15 linked databases&lt;/strong&gt; covering every facet of a one‑person business:

&lt;ul&gt;
&lt;li&gt;Clients, Deals, Projects, Tasks, Meeting Notes&lt;/li&gt;
&lt;li&gt;Income, Expenses, Monthly P&amp;amp;L, Content Calendar, Ideas Inbox&lt;/li&gt;
&lt;li&gt;SOPs, Tool Stack, Quarterly Goals, Milestones, Weekly Reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;dashboard&lt;/strong&gt; that surfaces upcoming tasks, pipeline health, and cash flow at a glance.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;weekly review template&lt;/strong&gt; that prompts you to capture wins, flag overdue follow‑ups, and plan the next week in five minutes.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;quarterly goal framework&lt;/strong&gt; that ties daily actions to larger objectives without extra spreadsheets.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;PDF setup guide&lt;/strong&gt; that walks you through connecting your existing contacts, linking Stripe payouts, and customizing views—no coding required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All data lives in one place, so you no longer need to copy between a CRM and a spreadsheet or hunt for a meeting note buried in a separate notes app. Follow‑ups are automated by a filtered view that highlights any deal without a recent touchpoint, and the weekly review template reminds you to close the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solo Operator OS&lt;/strong&gt; is the operating system for solo entrepreneurs who are tired of app overload and missed follow‑ups. Import it into your Notion account, run the PDF guide once, and you’ll have a unified workspace that tracks clients, finances, content, and goals without the mental overhead of juggling five separate tools.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to stop the app shuffle? Import Solo Operator OS today and get your business running on a single, coherent Notion workspace.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Skip the build — get the ready-made version: &lt;a href="https://coldriver8.gumroad.com/l/solo-operator-os" rel="noopener noreferrer"&gt;https://coldriver8.gumroad.com/l/solo-operator-os&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>notion</category>
      <category>solopreneur</category>
      <category>workflow</category>
    </item>
  </channel>
</rss>
