{"id":576,"date":"2025-08-18T10:15:11","date_gmt":"2025-08-18T10:15:11","guid":{"rendered":"https:\/\/yer.ac\/blog\/?p=576"},"modified":"2025-08-18T10:50:55","modified_gmt":"2025-08-18T10:50:55","slug":"vibing-in-kiro-to-create-a-self-serve-portainer-wrapper","status":"publish","type":"post","link":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/","title":{"rendered":"Vibing in Kiro to create a self-serve Portainer wrapper."},"content":{"rendered":"\n<p>I recently (finally) got my invite to <a href=\"http:\/\/Kiro.Dev\">Kiro<\/a>, Amazon\u2019s new agentic IDE. I\u2019d tinkered with it, but never had a real use case outside of the usual \u201cmake me a to-do app\u201d test.<\/p>\n\n\n\n<p>Then Friday afternoon rolled around. With 30 minutes to go, I was asked to start parts of our Docker infrastructure. It\u2019s a simple click in Portainer, which got me thinking\u2026 <em>we should automate this<\/em>. Developers can sign in and toggle services on or off, but that still blocks non-technical users from self-serving. Why isn\u2019t there a <em>simple <\/em>dashboard for stopping (scale to zero), starting (scale to 1), or restarting a Portainer service? At least, not one that doesn\u2019t require infra changes or digging through old repos &#8211; and again, this is Friday afternoon.<\/p>\n\n\n\n<p class=\"has-pale-cyan-blue-background-color has-background\">As an aside, our internal environments have a 1:1 mapping between service and container, so service-level actions are the quickest path. Your mileage may vary. This post is also more \u201cmental notes\u201d than tutorial.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-amber-background-color has-background\">I wrote this before the recent Kiro announcement, see the final concluding thoughts. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Validating with PowerShell first<\/h2>\n\n\n\n<p>First I wanted to confirm that Portainer exposes an API I could hit to scale services. If that worked, I could feed it into Kiro later.I used ChatGPT to outline the Portainer API boundaries, then had it generate a small PowerShell harness to prove the calls and auth. Straightforward enough: API key (Portainer \u2192 Account \u2192 Keys), your Environment ID, and a service name.<\/p>\n\n\n\n<p>The gist is:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Find the service by name<\/li>\n\n\n\n<li>Pull its spec\/version<\/li>\n\n\n\n<li>Change the replica count (0 = stop, 1 = start)<\/li>\n\n\n\n<li>Update the service<\/li>\n\n\n\n<li>Verify<\/li>\n<\/ol>\n\n\n\n<p>With that working \u2014 and being able to scale to 0 or 1 easily \u2014 I dusted off my Kiro preview access.<\/p>\n\n\n\n<p>Code below for reference:<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background\"><code>$BASE    = 'http:\/\/portainer:9000\/api'\n$HEADERS = @{ 'X-API-Key' = 'your-key' }\n$envId   = 8    # change this to the environment id\n$svcName = 'PRODUCT-SIT' # change this to the service name under the environment\n\n# 1) Find the service by name\n$filters = @{ name = @{ $svcName = $true } } | ConvertTo-Json -Compress\n$enc     = &#91;uri]::EscapeDataString($filters)\n$svcList = Invoke-RestMethod -Headers $HEADERS -Uri \"$BASE\/endpoints\/$envId\/docker\/services?filters=$enc\"\n\nif (-not $svcList) { throw \"Service '$svcName' not found on endpoint $envId.\" }\n$svcId = $svcList&#91;0].ID\n\n# 2) Get full spec and version\n$svc = Invoke-RestMethod -Headers $HEADERS -Uri \"$BASE\/endpoints\/$envId\/docker\/services\/$svcId\"\n\n# 3) Set replicas to 0 (replicated mode only)\n\nif ($svc.Spec.Mode.Replicated -eq $null) { throw \"Service is not replicated.\" }\nWrite-Host $svc\n$svc.Spec.Mode.Replicated.Replicas = 0\n\n# 4) Update the service with the new spec\n$ver = $svc.Version.Index\n$body = $svc.Spec | ConvertTo-Json -Depth 100\n\nWrite-Host $body\n\nInvoke-RestMethod -Method Post -Headers $HEADERS -ContentType 'application\/json' `\n  -Uri \"$BASE\/endpoints\/$envId\/docker\/services\/$svcId\/update?version=$ver\" -Body $body\n\n# 5) Optional verify\n$verify = Invoke-RestMethod -Headers $HEADERS -Uri \"$BASE\/endpoints\/$envId\/docker\/services\/$svcId\"\n$verify.Spec.Mode.Replicated.Replicas<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Spec driven Vibe Coding with Kiro<\/h2>\n\n\n\n<p>Kiro is another fork of VS Code with agentic AI built in, similar to Cursor. Where Kiro differs is its <strong>Spec Mode<\/strong>. We can write a compact prompt that defines data, actions, states, and constraints, and the agent treats it like a contract. For my prompt, I kept it rough. I just pasted my PowerShell as the API reference and wrote:<\/p>\n\n\n\n<pre class=\"wp-block-code has-cyan-bluish-gray-background-color has-background\"><code>I can turn off\/on my docker instances by altering the scale set to 0 or 1 in portainer.\nI have a script to do this like this:\n{{ powershell code }}\nI would like system where I have a JSOn config file which holds the URL, APIKEY, and a list of service names.\nThen I want a UI for this which will:\n- Show all the services\n- Indicator to say if the svc is running or not (based on the scale &gt;0)\n- An option to turn off (scale 0), turn on (scale 1), or restart (scale 0, wait, scale 1)<\/code><\/pre>\n\n\n\n<p>First, Kiro will convert the prompt into system requirements. Each requirement will have its own Acceptance Criteria and will be written in a BDD syntax.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1116\" height=\"980\" data-attachment-id=\"580\" data-permalink=\"https:\/\/yer.ac\/blog\/1-req\/\" data-orig-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req.png?fit=1116%2C980&amp;ssl=1\" data-orig-size=\"1116,980\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"1-req\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req.png?fit=300%2C263&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req.png?fit=700%2C615&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req.png?resize=1116%2C980\" alt=\"\" class=\"wp-image-580\" style=\"width:560px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Once we are happy with the Requirements and validated its what we want, we can move to <strong>Design<\/strong> Phase. The design phase validates the technology choices it will use for the frontend and backend, designs and maps out the interactions between components with some sample code, and finally it adds details on testing strategy which it will use to validate itself as it goes. Here we can make any amendments (I didn&#8217;t) before moving on. Interestingly, over every test I did it defaulted to Node\/React\/Typescript in every instance.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1114\" height=\"980\" data-attachment-id=\"579\" data-permalink=\"https:\/\/yer.ac\/blog\/1-req-2\/\" data-orig-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-1.png?fit=1114%2C980&amp;ssl=1\" data-orig-size=\"1114,980\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"1-req\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-1.png?fit=300%2C264&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-1.png?fit=700%2C616&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-1.png?resize=1114%2C980\" alt=\"\" class=\"wp-image-579\" style=\"width:566px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>The final phase is the <strong>Task List <\/strong>and is where we can see all the steps the agent will execute to be able to achieve the end-goal.  Each step has sub-tasks and a link back to the requirement for self-validation and guidance.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1120\" height=\"995\" data-attachment-id=\"581\" data-permalink=\"https:\/\/yer.ac\/blog\/1-req-3\/\" data-orig-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-2.png?fit=1120%2C995&amp;ssl=1\" data-orig-size=\"1120,995\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"1-req\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-2.png?fit=300%2C267&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-2.png?fit=700%2C622&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-2.png?resize=1120%2C995\" alt=\"\" class=\"wp-image-581\" style=\"width:582px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Whilst the screenshot above shows me post-execution, its simply a case of selecting &#8220;Start Task&#8221; next to an uncompleted item to kick the process off<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"979\" height=\"179\" data-attachment-id=\"582\" data-permalink=\"https:\/\/yer.ac\/blog\/1-req-4\/\" data-orig-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-3.png?fit=979%2C179&amp;ssl=1\" data-orig-size=\"979,179\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"1-req\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-3.png?fit=300%2C55&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-3.png?fit=700%2C128&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/1-req-3.png?resize=979%2C179\" alt=\"\" class=\"wp-image-582\" style=\"width:594px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Kiro also supports <strong>steering rules<\/strong> for guardrails and native <strong>MCP<\/strong> integration to plug in tools, APIs, and docs (Although I didn&#8217;t make use of this). <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Let the magic happen&#8230;<\/h2>\n\n\n\n<p>I kicked things off by running the tasks in order \u2014 starting with the basic scaffolding and React setup. As it progressed, Kiro validated its own work with unit tests and checks along the way.<\/p>\n\n\n\n<p>Like most agentic IDEs, Kiro can\u2019t run commands without approval. Whenever it needed to spin up a server, run tests, or interact with the system, it prompted me first. You can add commands to a trust list, either as one-offs or using wildcards \u2014 for example, trusting all <code>npm *<\/code> commands versus just <code>npm start<\/code>. That means over time it can run more autonomously on repeat runs.<\/p>\n\n\n\n<p>There were a few hiccups though. A common one was getting stuck on steps like \u201cValidating that Express server starts,\u201d since it doesn\u2019t seem to realise the terminal is tied up if the command keeps running. Similarly, Jest sometimes hung after running tests, waiting for me to manually stop it -which left Kiro stuck as well.<\/p>\n\n\n\n<p>When I intervened (Ctrl+C to the rescue), it usually recovered and checked the terminal output to confirm whether tests had passed. But a couple of times it jumped to the wrong conclusion and marked everything as fine despite failures &#8211; I assume because there was no proper exit code or error thrown.<\/p>\n\n\n\n<p>In those moments, the chat window proved useful. For example, when it couldn\u2019t confirm if the server was running (because the terminal was still tied up with <code>npm start<\/code>), I told it to try <code>curl<\/code> instead. It did, and even remembered that approach for later runs.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"766\" height=\"419\" data-attachment-id=\"583\" data-permalink=\"https:\/\/yer.ac\/blog\/untitled\/\" data-orig-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled.png?fit=766%2C419&amp;ssl=1\" data-orig-size=\"766,419\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Untitled\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled.png?fit=300%2C164&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled.png?fit=700%2C383&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled.png?resize=766%2C419\" alt=\"\" class=\"wp-image-583\" style=\"width:694px;height:auto\"\/><\/figure>\n<\/div>\n\n\n<p>Interestingly, when I asked &#8220;<em>Are you stuck?<\/em>&#8220;, the agent responded quite sarcastically telling me &#8220;<em>I&#8217;m not stuck thank you, I am just busy!!<\/em>&#8220;.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The end result<\/h2>\n\n\n\n<p>The end result was quite impressive really given my <em>very basic<\/em> prompt. I updated my config with a few of our environments and ran the start command. This is way closer to a working system than I got from other agents.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"2549\" height=\"1379\" data-attachment-id=\"584\" data-permalink=\"https:\/\/yer.ac\/blog\/untitled-2\/\" data-orig-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=2549%2C1379&amp;ssl=1\" data-orig-size=\"2549,1379\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Untitled\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=300%2C162&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=700%2C379&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?resize=2549%2C1379\" alt=\"\" class=\"wp-image-584\"\/><\/figure>\n\n\n\n<p>It was 99% the way there, the only thing that didn&#8217;t work well was the restart which was simply sending a malformed model to the API resulting in a HTTP 400. I simply switched to Kiro chat and told it there was a problem in a specific file and gave it the API info again and it corrected itself. I don&#8217;t know if I can blame Kiro for that though as I was hesitate to give it my real API key for testing until the very end in a human controlled test &#8211; I didn&#8217;t want it going rogue and messing up our environments!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final thoughts on the output<\/h2>\n\n\n\n<p>I mean, it did what I asked. In under half an hour on a Friday, with nothing more from me than a rough couple hundred characters and the occasional tap on \u201cTrust command,\u201d it built a system that actually worked and would be enough for my team to self-service our test environments.<\/p>\n\n\n\n<p>Would I rely on this for anything beyond quick POCs? Probably not. It\u2019s a fun tool\/toy, and for an internal use case like this it got about 80% of the way there with almost zero effort from me. But if this were destined for production, I\u2019d need to review every file to be confident, and that\u2019s where I think these agents shine more as \u201cpeer coders\u201d than full replacements (For example I quite like Co-Pilot).<\/p>\n\n\n\n<p>There\u2019s also the risk of people shipping AI-generated slop without understanding it. Honestly, if I\u2019d been a younger dev, I might have looked at this output and thought it was ready to ship even though I wouldn\u2019t have had the React experience (I still don&#8217;t!) to properly validate it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">More importantly, Kiro&#8217;s priced themselves out.<\/h2>\n\n\n\n<p>I got <em>very lucky<\/em> with my timing here as literally <strong>that evening<\/strong> Kiro officially announced the <a href=\"https:\/\/kiro.dev\/changelog\/paid-tiers-and-waitlist-codes\/\">tiering and free-usage limits<\/a>, as well as the <a href=\"https:\/\/kiro.dev\/blog\/pricing-plans-are-live\/\">pricing plans.<\/a>  It looks like free users don&#8217;t get any spec requests at all (other than the 100 trial ones that expire), and even the $20 tier gets a mere 125 Spec requests and 225 Vibe requests. Even users on $40 pro accounts were finding that they were burning through their limits within an hour.<\/p>\n\n\n\n<p>The issue here is that this doesn&#8217;t equate to 125 Spec requests or 225 additional interactions through vibing, as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Each interaction could cost at last 1 spec and 1 vibe each.<\/li>\n\n\n\n<li>The agent re-validating itself uses credits<\/li>\n\n\n\n<li>Some specs\/vibes are consuming multiple credits during feedback loops &#8211; with one user finding they burnt through a lot of their allowance with <a href=\"https:\/\/www.reddit.com\/r\/kiroIDE\/comments\/1msllie\/very_expensive_with_nontransparent_pricing\/\">225 vibes equating to ~11 questions<\/a> and another burnt through &gt;100 vibe requests asking Kiro to validate its own requirements vs the design and task list, and many more burning through the allowance <a href=\"https:\/\/www.reddit.com\/r\/kiroIDE\/comments\/1msksw5\/renew_the_account_and_all_the_vibe_credits\/\">under an hour<\/a>.  Even the $200 plan seems low-value.<\/li>\n<\/ul>\n\n\n\n<p>It was met by quite a bit of backlash in places like the <a href=\"https:\/\/www.reddit.com\/r\/kiroIDE\/\">Kiro subreddit<\/a> and their official discord with many users stating how expensive the plans are, the lack of transparency and in general a bit of a rug-pull.<\/p>\n\n\n\n<p>Its a shame really as the hype was quite big and it <em>seemed<\/em> like it addressed a bunch of issues that Agentic IDEs like Cursor and Windsurf had. Just unfortunate that the pricing plan has essentially killed the product off before it got to its full release.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verbosity is suddenly an issue<\/h2>\n\n\n\n<p>I didn&#8217;t think much about this (More is better, right?!) until I came to edit this post. <\/p>\n\n\n\n<p>I did find that the task list was a little too verbose, and I didn&#8217;t actually run the final few tasks which were all about adding additional integration testing, logging, comprehensive unit tests (which it already had a lot of), and &#8220;productionisation readiness&#8221;.  Overall there were 10 main steps, with most having between 1 and 5 subtasks.<\/p>\n\n\n\n<p>There is nothing <em>wrong<\/em> with the requirements and task list &#8211; If I were writing a spec to give to another dev I would include all this information, but in a world where each interaction has a considerable cost there is even more risk that people will omit &#8220;non-critical-path&#8221; deliverables like testing and documentation like I did. In a world of AI slop where nobody understands the outputs, do we want to risk the omission of documentation? Also what happens if the model gets changed to default to enhanced unit testing? Suddenly the costs increase&#8230;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s next?<\/h2>\n\n\n\n<p>Honestly, despite the sour taste around Kiro\u2019s usage limits which makes me unlikely to stick with it for further enhancements, I do see a solid future for the self-service tool itself. It solved a real pain point in our Docker infrastructure, and with a few minor tweaks (maybe continuing the \u201czero-code\u201d approach with Co-Pilot?) and deploying it into our cluster, we\u2019ll be saving time without interrupting any critical path work.<strong> Overall, I\u2019d call the tool a success<\/strong> <strong>and a fun experience nonetheless.<\/strong><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>On a Friday afternoon with half an hour to spare, I tested Kiro, Amazon\u2019s new agentic IDE, against a real-world problem: giving my team a simple way to start and stop Docker services in Portainer. With nothing more than a rough prompt, a PowerShell script, and a few \u201cTrust command\u201d clicks, Kiro spun up a working self-service dashboard in under 30 minutes. It wasn\u2019t production-ready, but it showed how close agent-driven coding can get with minimal input \u2014 and why pricing and trust still matter more than the hype<\/p>\n","protected":false},"author":1,"featured_media":584,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[50,42,24,1],"tags":[52,53,31,51,54,10,55],"class_list":["post-576","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-docker","category-productivity","category-uncategorized","tag-agentic-ide","tag-ai","tag-docker","tag-kiro","tag-portainer","tag-powershell","tag-productivity"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Vibing in Kiro to create a self-serve Portainer wrapper. - yer.ac | Adventures of a developer, and other things.<\/title>\n<meta name=\"description\" content=\"Using an Agentic IDE to create a self-service Portainer dashboard\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Vibing in Kiro to create a self-serve Portainer wrapper. - yer.ac | Adventures of a developer, and other things.\" \/>\n<meta property=\"og:description\" content=\"Using an Agentic IDE to create a self-service Portainer dashboard\" \/>\n<meta property=\"og:url\" content=\"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/\" \/>\n<meta property=\"og:site_name\" content=\"yer.ac | Adventures of a developer, and other things.\" \/>\n<meta property=\"article:published_time\" content=\"2025-08-18T10:15:11+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-08-18T10:50:55+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2549\" \/>\n\t<meta property=\"og:image:height\" content=\"1379\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"yer.ac\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"yer.ac\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/\"},\"author\":{\"name\":\"yer.ac\",\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/#\\\/schema\\\/person\\\/4638b9d868c7d3747bd3bb01fbc8153d\"},\"headline\":\"Vibing in Kiro to create a self-serve Portainer wrapper.\",\"datePublished\":\"2025-08-18T10:15:11+00:00\",\"dateModified\":\"2025-08-18T10:50:55+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/\"},\"wordCount\":1714,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/#\\\/schema\\\/person\\\/4638b9d868c7d3747bd3bb01fbc8153d\"},\"image\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/yer.ac\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Untitled-1.png?fit=2549%2C1379&ssl=1\",\"keywords\":[\"Agentic IDE\",\"AI\",\"docker\",\"Kiro\",\"Portainer\",\"PowerShell\",\"Productivity\"],\"articleSection\":[\"AI\",\"Docker\",\"Productivity\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/\",\"url\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/\",\"name\":\"Vibing in Kiro to create a self-serve Portainer wrapper. - yer.ac | Adventures of a developer, and other things.\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/yer.ac\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Untitled-1.png?fit=2549%2C1379&ssl=1\",\"datePublished\":\"2025-08-18T10:15:11+00:00\",\"dateModified\":\"2025-08-18T10:50:55+00:00\",\"description\":\"Using an Agentic IDE to create a self-service Portainer dashboard\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#primaryimage\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/yer.ac\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Untitled-1.png?fit=2549%2C1379&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/yer.ac\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/08\\\/Untitled-1.png?fit=2549%2C1379&ssl=1\",\"width\":2549,\"height\":1379},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/2025\\\/08\\\/18\\\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/yer.ac\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Vibing in Kiro to create a self-serve Portainer wrapper.\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/yer.ac\\\/blog\\\/\",\"name\":\"yer.ac | Adventures of a developer, and other things.\",\"description\":\"Blog to keep track of things I am upto\",\"publisher\":{\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/#\\\/schema\\\/person\\\/4638b9d868c7d3747bd3bb01fbc8153d\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/yer.ac\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/yer.ac\\\/blog\\\/#\\\/schema\\\/person\\\/4638b9d868c7d3747bd3bb01fbc8153d\",\"name\":\"yer.ac\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg\",\"caption\":\"yer.ac\"},\"logo\":{\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Vibing in Kiro to create a self-serve Portainer wrapper. - yer.ac | Adventures of a developer, and other things.","description":"Using an Agentic IDE to create a self-service Portainer dashboard","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/","og_locale":"en_US","og_type":"article","og_title":"Vibing in Kiro to create a self-serve Portainer wrapper. - yer.ac | Adventures of a developer, and other things.","og_description":"Using an Agentic IDE to create a self-service Portainer dashboard","og_url":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/","og_site_name":"yer.ac | Adventures of a developer, and other things.","article_published_time":"2025-08-18T10:15:11+00:00","article_modified_time":"2025-08-18T10:50:55+00:00","og_image":[{"width":2549,"height":1379,"url":"http:\/\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png","type":"image\/png"}],"author":"yer.ac","twitter_card":"summary_large_image","twitter_misc":{"Written by":"yer.ac","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#article","isPartOf":{"@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/"},"author":{"name":"yer.ac","@id":"https:\/\/yer.ac\/blog\/#\/schema\/person\/4638b9d868c7d3747bd3bb01fbc8153d"},"headline":"Vibing in Kiro to create a self-serve Portainer wrapper.","datePublished":"2025-08-18T10:15:11+00:00","dateModified":"2025-08-18T10:50:55+00:00","mainEntityOfPage":{"@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/"},"wordCount":1714,"commentCount":1,"publisher":{"@id":"https:\/\/yer.ac\/blog\/#\/schema\/person\/4638b9d868c7d3747bd3bb01fbc8153d"},"image":{"@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=2549%2C1379&ssl=1","keywords":["Agentic IDE","AI","docker","Kiro","Portainer","PowerShell","Productivity"],"articleSection":["AI","Docker","Productivity"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/","url":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/","name":"Vibing in Kiro to create a self-serve Portainer wrapper. - yer.ac | Adventures of a developer, and other things.","isPartOf":{"@id":"https:\/\/yer.ac\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#primaryimage"},"image":{"@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=2549%2C1379&ssl=1","datePublished":"2025-08-18T10:15:11+00:00","dateModified":"2025-08-18T10:50:55+00:00","description":"Using an Agentic IDE to create a self-service Portainer dashboard","breadcrumb":{"@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#primaryimage","url":"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=2549%2C1379&ssl=1","contentUrl":"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=2549%2C1379&ssl=1","width":2549,"height":1379},{"@type":"BreadcrumbList","@id":"https:\/\/yer.ac\/blog\/2025\/08\/18\/vibing-in-kiro-to-create-a-self-serve-portainer-wrapper\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/yer.ac\/blog\/"},{"@type":"ListItem","position":2,"name":"Vibing in Kiro to create a self-serve Portainer wrapper."}]},{"@type":"WebSite","@id":"https:\/\/yer.ac\/blog\/#website","url":"https:\/\/yer.ac\/blog\/","name":"yer.ac | Adventures of a developer, and other things.","description":"Blog to keep track of things I am upto","publisher":{"@id":"https:\/\/yer.ac\/blog\/#\/schema\/person\/4638b9d868c7d3747bd3bb01fbc8153d"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/yer.ac\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/yer.ac\/blog\/#\/schema\/person\/4638b9d868c7d3747bd3bb01fbc8153d","name":"yer.ac","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg","caption":"yer.ac"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/67ed010c9cc7986d40647e061c6dcdb06d818776591c7e954055adb629621113?s=96&d=retro&r=pg"}}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/yer.ac\/blog\/wp-content\/uploads\/2025\/08\/Untitled-1.png?fit=2549%2C1379&ssl=1","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/paP5IW-9i","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/posts\/576","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/comments?post=576"}],"version-history":[{"count":8,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/posts\/576\/revisions"}],"predecessor-version":[{"id":592,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/posts\/576\/revisions\/592"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/media\/584"}],"wp:attachment":[{"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/media?parent=576"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/categories?post=576"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yer.ac\/blog\/wp-json\/wp\/v2\/tags?post=576"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}