<?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: Rohan</title>
    <description>The latest articles on DEV Community by Rohan (@rohan_roots).</description>
    <link>https://dev.to/rohan_roots</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%2F4123136%2F89018fe9-fab6-48bc-a338-acc329fb443b.jpg</url>
      <title>DEV Community: Rohan</title>
      <link>https://dev.to/rohan_roots</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rohan_roots"/>
    <language>en</language>
    <item>
      <title>Behind a Grafana Dashboard Migration: What JSON Can't Do</title>
      <dc:creator>Rohan</dc:creator>
      <pubDate>Mon, 28 Sep 2026 13:25:41 +0000</pubDate>
      <link>https://dev.to/rohan_roots/behind-a-grafana-dashboard-migration-what-json-cant-do-50hb</link>
      <guid>https://dev.to/rohan_roots/behind-a-grafana-dashboard-migration-what-json-cant-do-50hb</guid>
      <description>&lt;p&gt;&lt;a href="/blog/grafana-cover.png" class="article-body-image-wrapper"&gt;&lt;img src="/blog/grafana-cover.png" alt="Editorial illustration of dashboard panels moving between monitoring systems, with cables, security controls, certificates, and validation work around the transfer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;strong&gt;~140&lt;/strong&gt; dashboards&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;3&lt;/strong&gt; tracker views&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;v11 → v12&lt;/strong&gt; Grafana versions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;On paper, the job sounded simple: export the JSON, import it, and move on to the next dashboard.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That description left out most of the work.&lt;/p&gt;

&lt;p&gt;I was moving roughly 140 dashboards from a Grafana OSS v11 instance to a Grafana Enterprise v12 instance. They were spread across folders owned by different applications, and copying every dashboard was never the goal. I had to find out which ones were still used, get confirmation from the application teams, keep non-production content out of production, and move only the dashboards that should remain.&lt;/p&gt;

&lt;p&gt;Once I started, most of the job happened around the JSON. Every dashboard came with dependencies, an owner who had to make a decision, and a chance of landing in the wrong environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first inventory changed the plan
&lt;/h2&gt;

&lt;p&gt;Before moving a dashboard, I compared the data sources it referenced with the data sources available in the target instance. Clicking through the Grafana screens one by one would have taken forever, so I opened the browser developer tools, docked the console beside each Grafana instance, and ran small JavaScript calls against the Grafana HTTP API from my authenticated browser session.&lt;/p&gt;

&lt;p&gt;I pulled the data-source metadata from Grafana v11 and v12, kept comparable fields such as name, type, and unique identifier, and diffed the two lists. The administration team had already said the data sources were migrated. The comparison showed otherwise, which was useful to learn before the first dashboard import.&lt;/p&gt;

&lt;p&gt;Some dashboard data sources were missing from the new instance. Others were still under review because nobody had confirmed whether they were active. Waiting for every dependency would have held up dashboards that were ready to move, and the deadline did not leave much room for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first useful rule:&lt;/strong&gt; Do not build a throwaway dashboard just to see whether a data source connects. Run Save &amp;amp; test when you have access. When you do not, assign that check to the administration team and track the handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  I moved the least dependent dashboards first
&lt;/h2&gt;

&lt;p&gt;I split the 140 dashboards into smaller batches based on dependency risk. The first batch came from folders where the required data sources already existed and the application owners had confirmed that the dashboards were still needed.&lt;/p&gt;

&lt;p&gt;Starting there kept the project moving while the missing dependencies were sorted out. It also gave me a clean path to validate the target with lower-risk dashboards. Anything blocked stayed marked as blocked instead of disappearing into one large import and becoming tomorrow's mystery.&lt;/p&gt;

&lt;p&gt;I still did not consider the target fully ready. This was a controlled pilot that let me make progress under the deadline without hiding the unfinished work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The jump from Grafana v11 to v12 changed the operator workflow
&lt;/h2&gt;

&lt;p&gt;The source was Grafana OSS v11 and the target was Grafana Enterprise v12, so I was also crossing a major-version boundary. Even when an imported dashboard rendered correctly, the interface changed where I found actions and how I moved between viewing, editing, and validation. I kept reaching for controls where they used to be, which added time to every manual check.&lt;/p&gt;

&lt;p&gt;Grafana v12 uses the Scenes-based dashboard architecture introduced in Grafana v11 and brings a newer dashboard experience. The enabled feature toggles can expose a dashboard outline, tabs, conditional rendering, an auto-grid layout, and a context-aware editing pane. Those changes affected how I worked in the target instance, but they did not tell me whether an imported dashboard was healthy.&lt;/p&gt;

&lt;p&gt;Crossing versions also made the JSON checks more important. Grafana v12 can use a newer dashboard schema when Dynamic Dashboards and the new dashboards API are enabled. That feature was still documented as experimental, and saving a dashboard under it could convert the model in a way that affected schema-specific automation. Before troubleshooting any production export or import failure, I would record the exact 11.x and 12.x patch versions, feature toggles, dashboard schema version, and plugin versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The human migration mattered too:&lt;/strong&gt; Leave time to relearn navigation and editing in Grafana v12. An import can succeed while the operating workflow still needs to be checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  A data source can exist and still be unusable
&lt;/h2&gt;

&lt;p&gt;One of the more irritating discoveries was that a data source could appear in Grafana and still be unusable. Some were listed in the new instance, yet their panel queries failed. The username and password were there, but the connection also depended on certificate-related settings. Without the required trust or client-certificate configuration, authentication could not complete, so the dashboards stayed broken.&lt;/p&gt;

&lt;p&gt;I changed the readiness status after that. Seeing a data-source name in Grafana was not enough. I wanted a successful Save &amp;amp; test result in the target instance, then a representative dashboard query. Depending on the backend, a failure can come from the endpoint, credentials, Transport Layer Security (TLS) trust, client certificates, network reachability, headers, or plugin configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The better status model:&lt;/strong&gt; Use these states: missing, present but untested, passed Save &amp;amp; test, validated through a representative panel query, or blocked with an owner and next action.&lt;/p&gt;

&lt;h2&gt;
  
  
  My access boundary became part of the migration design
&lt;/h2&gt;

&lt;p&gt;My access also changed between the two instances. In v11, I could open a data source and run Save &amp;amp; test. In v12, that action was unavailable to me. I could work with dashboards and capture failed panel queries, but I could not run Grafana's built-in connection test or inspect the target data-source configuration myself.&lt;/p&gt;

&lt;p&gt;Save &amp;amp; test checks whether Grafana can reach and authenticate to the configured backend, so there was no reason to create a temporary dashboard just for connectivity. I still needed a representative dashboard query afterward because that checks the dashboard path as well.&lt;/p&gt;

&lt;p&gt;The restriction was reasonable, although it meant every target-side configuration issue depended on the administration team. I would handle that by agreeing on the validation handoff before a migration starts. The handoff should name the person who runs Save &amp;amp; test, the result or error to capture, the owner of the correction, and the point when the data source is ready for panel-level validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failures needed to stay specific
&lt;/h2&gt;

&lt;p&gt;Two failures were still unresolved. One dashboard rendered normally, but its export returned an undefined result. Grafana rejected another dashboard during import.&lt;/p&gt;

&lt;p&gt;I left both issues open rather than guessing at a cause. To narrow them down, I still needed the exact error text, the browser console and network response, the exported JSON, the source and target Grafana versions, the panel plugins involved, and the destination folder and data-source mappings.&lt;/p&gt;

&lt;p&gt;For the import failure, I would also check dashboard identifiers and version fields, folder identifiers, permissions, plugin compatibility, and data-source names or unique identifiers. If the user interface keeps giving an unhelpful result, I would try the supported API to see whether the failure comes from the interface or the dashboard model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not turn a hypothesis into a postmortem:&lt;/strong&gt; Keep the issue open until the evidence shows which layer failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part was deciding what deserved to move
&lt;/h2&gt;

&lt;p&gt;The request was clear about one thing: an existing dashboard did not automatically deserve a place in the new instance.&lt;/p&gt;

&lt;p&gt;Each application team had to confirm which dashboards were still useful, and I had to keep non-production dashboards out of the production folder structure. Much of that work happened in conversations with owners. Valid JSON did not make a dashboard in scope, and a successful import could still put the wrong thing in the wrong place.&lt;/p&gt;

&lt;p&gt;The review also exposed dashboards that nobody needed anymore. Copying them into the enterprise instance would have carried over stale ownership and the false comfort of dashboards that nobody maintained. I used the migration to leave that clutter behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workbook became the control plane
&lt;/h2&gt;

&lt;p&gt;The import itself often took less time than recording the decision around it. I kept that work in one Excel workbook with three worksheets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Summary:&lt;/strong&gt; A compact roll-up with the total dashboard count and separate counts for reviewed, migrated, skipped or not migrated, and blocked. It showed the overall position without making anyone read every dashboard row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard drill-down:&lt;/strong&gt; One row per dashboard, with columns for the dashboard name, source folder or application, environment, application-team decision, required data source, migration status, and blocker reference. I filtered this sheet by folder, environment, or status when I chose the next batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocker details:&lt;/strong&gt; One row per blocker, linked to the affected dashboard or dashboards. It captured the failure stage, exact error, evidence collected, previous attempts, current owner, next step, and resolution status. That kept the troubleshooting history out of free-form chat and stopped us from repeating the same checks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each sheet served a different reader. The summary showed overall progress, the drill-down showed the state of a specific dashboard, and the blocker sheet held the history and next action for anything stuck.&lt;/p&gt;

&lt;p&gt;Leadership could use the summary without parsing technical errors. I used the drill-down to build the next migration batch, while administrators could work from the blocker sheet without reconstructing an issue from messages or screenshots.&lt;/p&gt;

&lt;p&gt;By then, the workbook was doing real operational work. It showed me what had been skipped, where troubleshooting was being repeated, and which failures still had no owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot helped, but it did not own the workbook
&lt;/h2&gt;

&lt;p&gt;I used Microsoft Copilot to structure parts of the workbook and handle some formatting. It saved setup time when it worked. After several revisions, though, it sometimes lost the thread and asked me to upload the latest workbook again. Nothing improves a migration afternoon like being asked for the file you just supplied.&lt;/p&gt;

&lt;p&gt;It was useful for getting an initial structure onto the page, but I still checked every revision, kept the current file safe, and verified that formulas and status labels stayed consistent. A migration tracker that looks finished but contains the wrong status is worse than an unfinished one.&lt;/p&gt;

&lt;p&gt;I ended up using Copilot for small, contained tasks such as proposing columns, normalizing status values, generating a formula, or summarizing a blocker list. The workbook remained the source of truth, and I saved a new version before any broad rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would change on the next migration
&lt;/h2&gt;

&lt;p&gt;If I ran this migration again, I would put the following readiness gate in place before the first batch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inventory first:&lt;/strong&gt; List the dashboards, folders, owners, environments, data sources, plugins, and alert dependencies before migrating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freeze the version baseline:&lt;/strong&gt; Record the exact 11.x and 12.x patch releases, enabled feature toggles, dashboard schema version, and plugin versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map old to new:&lt;/strong&gt; Record the source and target data-source names or unique identifiers instead of relying on similar display names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test, do not count:&lt;/strong&gt; Run Save &amp;amp; test in the target data-source settings, then validate a representative panel query. If the migration owner lacks access, assign the first check to the administrator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pilot by risk:&lt;/strong&gt; Start with a small folder whose owners are known and whose dependencies are complete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture exact failures:&lt;/strong&gt; Keep the response text, console evidence, JSON, and ownership in the blocker log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate beyond the import:&lt;/strong&gt; Check variables, time ranges, links, transformations, panel queries, permissions, and environment boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate after the pattern is stable:&lt;/strong&gt; Use manual JSON export and import while discovering the pattern. For a repeatable migration at this scale, the HTTP API, provisioning, Terraform, or a Git-based workflow can reduce clicks and make changes reviewable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual JSON export and import worked well for the first pass because I could inspect each move and isolate failures. Once I understood the exceptions, I would shift toward a repeatable method rather than keep clicking through the same steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboards were only phase one
&lt;/h2&gt;

&lt;p&gt;Alert migration comes next, and I will handle it separately from the dashboard batch. A broken dashboard is usually visible when someone opens it. A bad alert migration can stay quiet, send duplicate notifications, or leave a production service uncovered.&lt;/p&gt;

&lt;p&gt;Before touching the alerts, I need a separate inventory of alert rules, data-source dependencies, contact points, notification policies, mute timings, ownership, and expected firing behavior. I also need a controlled overlap or cutover plan so the old rules stay active until the new path has been proven.&lt;/p&gt;

&lt;p&gt;For the alert phase, I will spend less time asking whether the configuration moved and more time proving that the new path behaves correctly. The old rules will stay in place until those checks are complete and the owning teams are comfortable with the cutover.&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>observability</category>
      <category>sre</category>
      <category>devops</category>
    </item>
    <item>
      <title>It Took Me Three Days to Install Ubuntu. Ubuntu Wasn't the Problem.</title>
      <dc:creator>Rohan</dc:creator>
      <pubDate>Mon, 21 Sep 2026 17:56:40 +0000</pubDate>
      <link>https://dev.to/rohan_roots/it-took-me-three-days-to-install-ubuntu-ubuntu-wasnt-the-problem-4p6d</link>
      <guid>https://dev.to/rohan_roots/it-took-me-three-days-to-install-ubuntu-ubuntu-wasnt-the-problem-4p6d</guid>
      <description>&lt;p&gt;&lt;em&gt;I thought this would take half an hour. Then the USB stick died, the download crawled, and Windows locked the replacement drive.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For months, my old Lenovo Yoga 720 had been doing what old laptops do best: lying around.&lt;/strong&gt; Its battery indicator was always stuck at 0 percent, so I did not think much about it. I had quietly accepted that its main job was now collecting dust.&lt;/p&gt;

&lt;p&gt;Then my X timeline filled up with people talking about Linux desktops. I remembered the laptop, looked at its aging Windows-era specs, and thought: this could still make a perfectly useful Linux machine.&lt;/p&gt;

&lt;p&gt;So, on Saturday afternoon, I gave myself half an hour to install Ubuntu. A few hours later, I caught myself asking whether this was really the best use of my Saturday.&lt;/p&gt;

&lt;p&gt;The answer was probably no. But by then I wanted to know what was actually wrong. I had several possible causes and still had not reached the Ubuntu installer.&lt;/p&gt;

&lt;p&gt;Across three calendar days, with two days of actual troubleshooting, I ran into three separate problems: a dying USB drive, a slow mirror, and a Windows lock on the replacement drive. All three left me at the same place: still no installer.&lt;/p&gt;

&lt;p&gt;I expected the install to be the easy part. It was. Getting trustworthy boot media onto a USB drive consumed nearly all the time.&lt;/p&gt;

&lt;p&gt;The problems looked related because they all stopped me at the same place. They were not. I had to check the USB stick, the download, and Windows separately instead of blaming the whole Ubuntu setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure chain, in order
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What actually happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;USB hardware&lt;/td&gt;
&lt;td&gt;Eight failed approaches ended with the old stick disconnecting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Download path&lt;/td&gt;
&lt;td&gt;Official mirror estimated about 35 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows state&lt;/td&gt;
&lt;td&gt;Replacement SanDisk was locked from Rufus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boot media&lt;/td&gt;
&lt;td&gt;A manual Windows format let Rufus write the image&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Eight failed attempts turned the first USB stick into the suspect
&lt;/h2&gt;

&lt;p&gt;The original 64 GB USB stick did not fail cleanly. It sent me through eight different attempts first. A standard Rufus flash ended with a vmlinuz corruption error. DD mode made the drive disappear from the F12 boot menu, while Windows Advanced Startup simply looped back to the desktop.&lt;/p&gt;

&lt;p&gt;Then I tried Ventoy. Running it from inside the compressed ZIP failed, so I extracted it and tried again with GPT selected. Ventoy answered with the impressively unhelpful message: 'An error occurred during the installation.'&lt;/p&gt;

&lt;p&gt;I used DiskPart to clean the old drive, created a FAT32 partition, and tried Ventoy again. Same error. Windows' normal format tool then said it could not complete the format. On the next force-wipe attempt, DiskPart cleaned the drive but failed during formatting with 'A device which does not exist was specified.' The USB stick had temporarily disconnected from Windows.&lt;/p&gt;

&lt;p&gt;I reinserted it and gave Ventoy one last chance. The drive mounted as an empty volume, but Ventoy failed again. It was never successfully installed on that stick.&lt;/p&gt;

&lt;p&gt;That final disconnect mattered more than the earlier error messages. A partitioning problem can make a drive look unusable, but hardware that vanishes while it is being written is a different kind of warning. By attempt eight, I was no longer troubleshooting. I was negotiating with a tiny piece of plastic.&lt;/p&gt;

&lt;p&gt;I stopped trying to rescue it and ordered a 64 GB SanDisk Ultra Flair from Amazon. It arrived the next day. At that point, Amazon's delivery pipeline was moving faster than my Ubuntu installation. Replacing the stick was the right decision, but it did not immediately solve the installation. It only removed the first fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 35-minute download was not my Wi-Fi
&lt;/h2&gt;

&lt;p&gt;The next delay looked like a network problem. The official Ubuntu download estimated roughly 35 minutes for the desktop image. That did not fit the connection I was using.&lt;/p&gt;

&lt;p&gt;A speed test on the same Wi-Fi showed about 264 Mbps. The laptop's connection was not the bottleneck. The server path was.&lt;/p&gt;

&lt;p&gt;I switched to the Massachusetts Institute of Technology mirror and downloaded the Ubuntu 26.04.1 desktop ISO in about three minutes. Same laptop. Same network. Different source. Thirty-two minutes disappeared without a single router reboot, which is usually a good clue that the router was innocent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A fast internet connection does not guarantee a fast download. The endpoint still gets a vote.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The new USB drive failed for a completely different reason
&lt;/h2&gt;

&lt;p&gt;With a healthy replacement drive and a freshly downloaded ISO, Rufus should have been routine. I selected the image, used GPT for a Unified Extensible Firmware Interface (UEFI) system, and started the write.&lt;/p&gt;

&lt;p&gt;Rufus reported that it could not access the drive because another process was using it. Closing File Explorer, unplugging and reconnecting the stick, and running Rufus as administrator did not clear the error.&lt;/p&gt;

&lt;p&gt;This was the moment when it would have been easy to assume the new USB stick was also bad and start pricing a third one. The symptom looked familiar, but the evidence did not. The drive stayed connected and Rufus could identify it. This was a lock or partition-state problem inside Windows, not failing flash memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  A normal Windows format unlocked Rufus
&lt;/h2&gt;

&lt;p&gt;I did not run DiskPart clean on the new USB drive. I opened This PC, right-clicked the drive, and formatted it through the normal Windows menu.&lt;/p&gt;

&lt;p&gt;After that manual format, Rufus started writing normally. Windows briefly reported that the USB drive was not accessible while Rufus repartitioned it. In this context, that popup was expected. Rufus was actively replacing the filesystem Windows had just mounted.&lt;/p&gt;

&lt;p&gt;The progress bar reached the green READY state. The replacement drive had never been broken. It had only needed a regular format before Rufus could use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Once the boot media worked, Ubuntu installed without drama
&lt;/h2&gt;

&lt;p&gt;I selected the USB drive from the Lenovo Yoga 720 boot menu. Ubuntu loaded into the live environment, and the installer started.&lt;/p&gt;

&lt;p&gt;This laptop was being repurposed, not set up for dual boot, so I erased the internal disk and gave Ubuntu the whole machine. The installation completed, the laptop restarted, and Ubuntu 26.04.1 LTS booted from the internal drive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwjv6wxko51lytcsy6kb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwjv6wxko51lytcsy6kb.png" alt="Ubuntu 26.04.1 LTS, finally running on the Lenovo Yoga 720" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ubuntu 26.04.1 LTS, finally running on the Lenovo Yoga 720. The first welcome came with 37 updates waiting.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After all that, the first notification said '37 Updates Available.' Three days to install the operating system, and it welcomed me by giving me more work. Fair enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful lesson was to troubleshoot the layer, not the symptom
&lt;/h2&gt;

&lt;p&gt;The repeated outcome was "Ubuntu is still not installed," but the causes belonged to different layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware:&lt;/strong&gt; The old USB stick disconnected during writes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network path:&lt;/strong&gt; The selected mirror was slow even though the local connection was healthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operating-system state:&lt;/strong&gt; Windows held the replacement drive in a state Rufus could not use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boot configuration:&lt;/strong&gt; The finished USB needed to be selected from the UEFI boot menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What helped was checking the evidence again after every change. The old USB stick was bad. The new one was not. It had run into a separate Windows problem.&lt;/p&gt;

&lt;p&gt;I started on Saturday afternoon thinking Ubuntu would be installed in half an hour. Instead, I spent two days finding three unrelated problems before the installer even started. The actual installation was the shortest and least interesting part.&lt;/p&gt;

&lt;p&gt;Ubuntu had barely done anything wrong, which was slightly inconvenient after I had spent hours blaming the Ubuntu installation. The forgotten laptop now has a fresh Linux desktop, Windows is gone, and that questionable Saturday decision finally has something to show for it.&lt;/p&gt;

&lt;p&gt;A 30-second hold of the laptop's power button brought the battery indicator, which had been stuck showing 0 percent, back to life. The percentage moves again, but the battery still drains faster than it should. Apparently the Yoga has already chosen my next project: replace the battery.&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>linux</category>
      <category>sre</category>
      <category>debugging</category>
    </item>
    <item>
      <title>I Asked an AI Agent to Buy Me a VPS</title>
      <dc:creator>Rohan</dc:creator>
      <pubDate>Sun, 13 Sep 2026 12:33:56 +0000</pubDate>
      <link>https://dev.to/rohan_roots/i-asked-an-ai-agent-to-buy-me-a-vps-31ip</link>
      <guid>https://dev.to/rohan_roots/i-asked-an-ai-agent-to-buy-me-a-vps-31ip</guid>
      <description>&lt;p&gt;&lt;strong&gt;FIELD NOTES ON PRACTICAL AI AGENTS&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  I Asked an AI Agent to Buy Me a VPS
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;It compared providers, picked a $35.99-a-year server, filled out the checkout, then got stuck on two password fields.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I wanted a small always-on server for an AI agent and infrastructure experiments. Instead of opening fifteen tabs and building another comparison spreadsheet, I gave Muse the job. What followed felt less like chatting with a bot and more like supervising a very fast junior operator.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;PURPOSE&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Hermes Agent + server lab&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PROVIDER&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RackNerd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PLAN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 GB KVM VPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REGION&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;New York, USA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OPERATING SYSTEM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ubuntu 24.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PRICE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$35.99 per year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A one-line brief became a real purchase
&lt;/h2&gt;

&lt;p&gt;The request was deliberately simple: find a budget VPS in the United States with at least 2 GB of memory, able to run Nous Research's Hermes agent around the clock. Some latency was fine. Cost mattered more than prestige.&lt;/p&gt;

&lt;p&gt;That's the kind of job an agent should be good at. The goal is concrete, the constraints are visible, and a wrong pick is recoverable. This wasn't a production database or a six-figure cloud commitment. It was a small server that could become a durable sandbox for containers, networking, automation, system services, and whatever I wanted to break next.&lt;/p&gt;

&lt;p&gt;Muse didn't just return a list of providers. It asked the questions that changed the answer: where am I located, how much memory do I need, does latency matter, do I want monthly flexibility or the lowest annual price. Once those were settled, the comparison narrowed fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why RackNerd won
&lt;/h2&gt;

&lt;p&gt;It came down to RackNerd versus Hetzner. Hetzner is the stronger cloud platform: polished tooling, predictable infrastructure, hourly billing, a better path if the workload ever becomes important. But its best prices were in Europe, and I wanted a US location.&lt;/p&gt;

&lt;p&gt;RackNerd's annual special was the more pragmatic lab machine: 2 vCPUs, 2 GB of memory, 35 GB of storage, a dedicated IPv4 address, in New York, for $35.99 a year. That's roughly $3 a month. For an experiment box running a remote-model agent, it was enough machine at a price low enough to just leave running.&lt;/p&gt;

&lt;p&gt;The useful part of the recommendation was the trade-off, stated plainly. I was buying the cheapest suitable box, not the cheapest platform I'd trust with revenue. Different purchases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding a cheap VPS was the easy part. The useful part was carrying the decision from a vague intent all the way to a checkout-ready configuration.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkout worked, until two boxes looked identical
&lt;/h2&gt;

&lt;p&gt;After I confirmed the plan, Muse moved into execution. It set up the RackNerd account, selected New York, chose Ubuntu 24.04, carried the billing details into checkout, and paid through Stripe Link. I kept final approval over the charge, which is exactly where I wanted the human checkpoint.&lt;/p&gt;

&lt;p&gt;Then the whole thing stalled on the least glamorous problem imaginable: the Password and Confirm Password fields looked identical to the automation. Autofill couldn't tell them apart. I'd type the password, the page would clear it during validation, and we'd do the dance again. The agent had compared providers and reasoned through infrastructure trade-offs, and then two ordinary HTML inputs became the hard part.&lt;/p&gt;

&lt;p&gt;The workaround was the site's own password generator. That removed the ambiguous handoff, the form validated, and the order went through. The fix was mundane. The failure was the interesting part: agentic workflows rarely break on hard reasoning. They break at the seams between systems. Duplicated labels, session boundaries, CAPTCHAs, one-time codes, a field that doesn't expose enough structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payment succeeded, and I still checked it twice
&lt;/h2&gt;

&lt;p&gt;Minutes later the emails started arriving: welcome, order confirmation, invoice, payment receipt, server login details. So many messages after an automated checkout raised the obvious question: had I been charged twice?&lt;/p&gt;

&lt;p&gt;Muse found one completed $35.99 payment. The rest was the normal paper trail for a single order. That verification mattered as much as the purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right model is delegation with checkpoints
&lt;/h2&gt;

&lt;p&gt;This changed how I think about consumer AI agents. The value wasn't a fully autonomous system running without me. It was simpler: dozens of small tasks, compressed. Gathering constraints, comparing offers, checking locations, turning requirements into a specific config, filling routine fields, keeping the whole decision in one conversation.&lt;/p&gt;

&lt;p&gt;I stayed responsible for the parts that mattered. I approved the provider, the annual commitment, and the payment. Credentials moved through secure handoffs instead of chat. When the interface turned ambiguous, I stepped in. That division of labor felt less futuristic than the marketing, and much more useful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delegate bounded research.&lt;/strong&gt; Give the agent a goal, a budget, minimum specs, a location, and the trade-offs you'll accept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep approval at the edge.&lt;/strong&gt; The agent can prepare a purchase. You confirm the final config, price, renewal terms, and the charge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect interface friction.&lt;/strong&gt; A workflow can fail on a plain form after getting all the reasoning right. A clean manual handoff is a feature, not a defeat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the outcome.&lt;/strong&gt; Check the receipt, the account, the service details, and the payment history before calling it done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with recoverable tasks.&lt;/strong&gt; A cheap lab server is a better first autonomous purchase than a production system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The purchase is only the beginning
&lt;/h2&gt;

&lt;p&gt;Now the real work moves to the server: SSH in, create a non-root admin, switch to key-based auth, set up the firewall, turn on unattended security updates, and run Hermes under systemd so it survives reboots. After that I might host this blog on the same box, which would make the infrastructure behind this story the subject of the next one.&lt;/p&gt;

&lt;p&gt;There's a satisfying loop in that. An AI agent helped buy the server that will host another AI agent, and the bumps along the way made a better story than a frictionless demo would have. The password-field failure wasn't something to hide. It was the most honest part.&lt;/p&gt;

&lt;h2&gt;
  
  
  My takeaway: agents are already practical, just not invisible
&lt;/h2&gt;

&lt;p&gt;Muse didn't remove judgment, and it didn't turn a messy commercial website into a clean API. It removed most of the coordination cost between "I should get a server" and "the server is ordered." That's real progress.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
