<?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: Khanh Nguyen</title>
    <description>The latest articles on DEV Community by Khanh Nguyen (@khanh_nguyen_04b1205f5a91).</description>
    <link>https://dev.to/khanh_nguyen_04b1205f5a91</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%2F1559613%2F05e1dbdc-d265-4000-bdaf-e2cda4bacaea.jpg</url>
      <title>DEV Community: Khanh Nguyen</title>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khanh_nguyen_04b1205f5a91"/>
    <language>en</language>
    <item>
      <title>Building Calm in Complex Interfaces</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:22:04 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/building-calm-in-complex-interfaces-18n8</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/building-calm-in-complex-interfaces-18n8</guid>
      <description>&lt;p&gt;Complex interfaces do not need to feel complicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every detail a place
&lt;/h2&gt;

&lt;p&gt;The most useful systems create calm through repetition: a small set of spacing rules, clear type roles, and components that behave predictably when the content gets difficult.&lt;/p&gt;

&lt;p&gt;The goal is not to remove detail.&lt;br&gt;
The goal is to give every detail a place to land.&lt;/p&gt;

&lt;p&gt;I feel the difference most strongly in administrative tools and dashboards.&lt;br&gt;
These products often need to hold a lot of information, but density does not have to mean that every control competes for the first glance.&lt;br&gt;
The hierarchy can tell me what matters now, what I can inspect next, and what is available only when I need it.&lt;/p&gt;

&lt;p&gt;That hierarchy starts with language.&lt;br&gt;
Clear headings, useful labels, and consistent status names reduce the amount of translation a person has to do before acting.&lt;br&gt;
Spacing and type then reinforce the same structure so the interface does not depend on color or decoration alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calm is not quiet
&lt;/h2&gt;

&lt;p&gt;I have also learned that calm is not the same as quiet.&lt;br&gt;
A strong warning should be visible, a destructive action should feel different, and a changing value should give me enough feedback to trust what just happened.&lt;br&gt;
The goal is not to flatten every moment into the same tone.&lt;br&gt;
The goal is to make the difference between moments intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the next decision easier
&lt;/h2&gt;

&lt;p&gt;When a complex screen feels calm, I can focus on the decision in front of me instead of managing the interface around it.&lt;br&gt;
That is the standard I return to when a system starts accumulating one more panel, one more option, or one more exception.&lt;/p&gt;

</description>
      <category>design</category>
      <category>frontend</category>
      <category>ux</category>
    </item>
    <item>
      <title>Working in Public</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:21:33 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/working-in-public-5bfi</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/working-in-public-5bfi</guid>
      <description>&lt;p&gt;Sharing unfinished thinking makes the finished work stronger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share the useful context
&lt;/h2&gt;

&lt;p&gt;I do not mean publishing every rough thought or turning the work into a performance.&lt;br&gt;
For me, working in public is a lightweight habit of leaving enough context behind for someone else to understand how the work changed.&lt;/p&gt;

&lt;p&gt;Writing down the constraint, the discarded direction, and the reason behind a decision creates a useful record for future collaborators and for the person doing the work.&lt;br&gt;
It also makes my own reasoning more honest because vague instincts become easier to question once they are written down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the notes small
&lt;/h2&gt;

&lt;p&gt;The most useful notes are often small.&lt;br&gt;
I might share why I chose one layout, what failed during a deployment, or which assumption changed after seeing a real user try the flow.&lt;br&gt;
These notes do not need to sound finished to be valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a record over time
&lt;/h2&gt;

&lt;p&gt;The blog is a place to keep those notes alive.&lt;br&gt;
Over time, they become more than a collection of posts.&lt;br&gt;
They become a map of the decisions, tradeoffs, and lessons that would otherwise disappear as soon as the next project begins.&lt;/p&gt;

&lt;p&gt;The blog is a place to keep those notes alive.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>learning</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Small Details Ship with You</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:21:32 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/the-small-details-ship-with-you-18ce</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/the-small-details-ship-with-you-18ce</guid>
      <description>&lt;p&gt;The small details are not small once they are repeated across a whole product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notice what repeats
&lt;/h2&gt;

&lt;p&gt;A one-pixel alignment, a useful empty state, or a button that responds clearly can turn a technically complete interface into a considered one.&lt;br&gt;
The opposite is true as well.&lt;br&gt;
One confusing state repeated across a checkout, a dashboard, and an onboarding flow becomes a steady tax on trust.&lt;/p&gt;

&lt;p&gt;I notice these details most when I return to a screen after spending time away from the code.&lt;br&gt;
The implementation tells me what I built, but the interface tells me what the user has to carry in their head.&lt;br&gt;
That distance often reveals a missing loading state, an awkward label, or a layout that only worked because I knew where to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat craft as a habit
&lt;/h2&gt;

&lt;p&gt;Craft is the habit of noticing those moments before the user has to.&lt;br&gt;
It is not endless polishing or an excuse to delay shipping.&lt;br&gt;
It is the discipline of fixing the small points that will be encountered again and again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the attitude visible
&lt;/h2&gt;

&lt;p&gt;The last ten percent is where the product's attitude becomes visible.&lt;br&gt;
Does it explain itself when something goes wrong?&lt;br&gt;
Does it respect the user's time?&lt;br&gt;
Does it make the next action feel clear?&lt;br&gt;
Those questions are small on a ticket and large in the experience.&lt;/p&gt;

&lt;p&gt;Craft is the habit of noticing those moments before the user has to.&lt;/p&gt;

</description>
      <category>design</category>
      <category>frontend</category>
      <category>ux</category>
    </item>
    <item>
      <title>Ship the First Draft</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:21:01 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/ship-the-first-draft-apc</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/ship-the-first-draft-apc</guid>
      <description>&lt;p&gt;The first version is not a verdict on the idea.&lt;/p&gt;

&lt;p&gt;It is a question sent into the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the question concrete
&lt;/h2&gt;

&lt;p&gt;Shipping early creates better feedback than polishing in isolation, especially when the work sits at the intersection of product, design, and code.&lt;br&gt;
The first draft gives people something concrete to react to, and concrete reactions are usually more useful than another round of private speculation.&lt;/p&gt;

&lt;p&gt;This does not mean shipping carelessly.&lt;br&gt;
I still want the first version to be safe, understandable, and honest about what it can do.&lt;br&gt;
The point is to keep the scope small enough that the team can learn before the assumptions become expensive to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notice the perfectionism loop
&lt;/h2&gt;

&lt;p&gt;I have noticed that perfectionism often disguises itself as responsibility.&lt;br&gt;
It sounds like, "we should make this complete before anyone sees it," but sometimes it is really a way to avoid the uncertainty that comes with feedback.&lt;br&gt;
The draft interrupts that loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let reality answer
&lt;/h2&gt;

&lt;p&gt;After shipping, I look for the gap between what I expected and what people actually do.&lt;br&gt;
That gap is not a failure of the idea.&lt;br&gt;
It is the most valuable material for the next version.&lt;/p&gt;

</description>
      <category>design</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Designing for the In-Between</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:21:00 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/designing-for-the-in-between-2fb1</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/designing-for-the-in-between-2fb1</guid>
      <description>&lt;p&gt;Transitions, loading states, and empty moments are part of the product, not gaps around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the pause
&lt;/h2&gt;

&lt;p&gt;I used to treat these moments as cleanup work for the end of a project.&lt;br&gt;
That usually meant a spinner, a generic empty message, and a transition that technically worked but gave no sense of what was happening.&lt;/p&gt;

&lt;p&gt;The in-between is where people decide whether a product feels trustworthy.&lt;br&gt;
If a save takes a moment, the interface should explain that work is happening.&lt;br&gt;
If there is no data yet, the empty state should help someone understand what belongs there and what they can do next.&lt;/p&gt;

&lt;p&gt;When these edges receive the same care as the main path, an interface begins to feel calm and considered.&lt;br&gt;
The experience does not become slower or more elaborate; it simply becomes easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Include the real journey
&lt;/h2&gt;

&lt;p&gt;Now, when I sketch a flow, I include the pause after the action, the first visit before there is data, the failed request, and the return journey after an interruption.&lt;br&gt;
Those are not edge cases to hide from the design.&lt;br&gt;
They are part of the real product.&lt;/p&gt;

</description>
      <category>design</category>
      <category>product</category>
      <category>ux</category>
    </item>
    <item>
      <title>Keep the Surface Simple</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:20:30 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/keep-the-surface-simple-3jf6</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/keep-the-surface-simple-3jf6</guid>
      <description>&lt;p&gt;Every extra control asks someone to spend attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complexity belongs behind the surface
&lt;/h2&gt;

&lt;p&gt;I have learned this most clearly when working on screens that started small and slowly collected exceptions.&lt;br&gt;
Each new toggle felt reasonable on its own, but the combined surface became a quiet negotiation with the user.&lt;/p&gt;

&lt;p&gt;The job of a design system is not to hide capability, but to give each decision a clear place and a consistent shape.&lt;br&gt;
That means naming the primary action, moving secondary actions out of the way, and resisting the urge to expose every possible state at once.&lt;/p&gt;

&lt;p&gt;Simple surfaces are not empty surfaces.&lt;br&gt;
They are the result of making a lot of decisions before asking the user to make one.&lt;/p&gt;

&lt;p&gt;When I remove an option, I try to ask whether I am removing useful power or just removing visual noise.&lt;br&gt;
That distinction matters because good simplicity should make the next step easier, not make the product less capable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave room for focus
&lt;/h2&gt;

&lt;p&gt;I still get this wrong sometimes.&lt;br&gt;
The useful habit is to come back to the screen later, look at it without the context of implementation, and notice where my own attention starts to scatter.&lt;/p&gt;

</description>
      <category>design</category>
      <category>product</category>
      <category>ux</category>
    </item>
    <item>
      <title>Notes from a Brutalist Portfolio</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:20:28 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/notes-from-a-brutalist-portfolio-221i</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/notes-from-a-brutalist-portfolio-221i</guid>
      <description>&lt;p&gt;A portfolio should make a point before it makes a promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the visual language clear
&lt;/h2&gt;

&lt;p&gt;For this site, that point is directness: strong contrast, visible structure, and enough visual tension to make the work memorable without hiding it behind decoration.&lt;br&gt;
I wanted the first impression to feel like a clear opinion rather than a neutral template.&lt;/p&gt;

&lt;p&gt;The hard borders and oversized type are not there only to look bold.&lt;br&gt;
They create a visible frame around the work, which makes the hierarchy easier to feel and gives each project a stronger place on the page.&lt;br&gt;
The friction is intentional too.&lt;br&gt;
Not every element is softened into a rounded card or animated into view, because a little resistance can make the important parts easier to notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the work stay visible
&lt;/h2&gt;

&lt;p&gt;The system is intentionally opinionated so the projects can feel more distinct.&lt;br&gt;
That decision comes with a responsibility to keep the interface usable.&lt;br&gt;
Strong contrast still needs readable contrast, movement still needs restraint, and a visual idea still needs to survive on a small screen.&lt;/p&gt;

&lt;p&gt;I am still learning where the line is between character and noise.&lt;br&gt;
The useful test is whether the visual language helps someone remember the work and find their way through it.&lt;br&gt;
If the style becomes the only thing they remember, the portfolio has started talking over the projects.&lt;/p&gt;

</description>
      <category>design</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A Better Way to Review</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:19:56 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/a-better-way-to-review-mjn</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/a-better-way-to-review-mjn</guid>
      <description>&lt;p&gt;Good reviews make the work clearer without making the author smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with what is visible
&lt;/h2&gt;

&lt;p&gt;I try to separate questions, observations, and decisions so a review can move forward instead of becoming a debate about taste.&lt;br&gt;
That one distinction has changed the tone of my reviews more than any clever checklist.&lt;/p&gt;

&lt;p&gt;An observation describes what is visible: the button is hard to find, the error message appears below the fold, or the interaction behaves differently from the rest of the product.&lt;br&gt;
A question opens a door: what happens if this request fails, or could this action be available earlier in the flow?&lt;br&gt;
A decision closes a loop: we will keep this layout, use the existing component, or revisit the problem in a separate task.&lt;/p&gt;

&lt;p&gt;When all three are mixed together, feedback can feel like a verdict.&lt;br&gt;
When they are separated, the author can respond to the actual issue instead of trying to decode the reviewer's mood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review against the goal
&lt;/h2&gt;

&lt;p&gt;I also try to review the work against its goal, not against my personal taste.&lt;br&gt;
The goal might be clarity, speed, accessibility, or a reliable way to recover from an error.&lt;br&gt;
That gives the conversation something more useful than preference to hold onto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make room for honesty
&lt;/h2&gt;

&lt;p&gt;Kindness matters here, but kindness is not the same as avoiding difficult feedback.&lt;br&gt;
The kindest review I can give is specific enough to help someone make the work better and respectful enough to keep the collaboration open.&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>productivity</category>
      <category>ux</category>
    </item>
    <item>
      <title>Accessible by Default</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 22 Sep 2026 03:19:54 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/accessible-by-default-1fhi</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/accessible-by-default-1fhi</guid>
      <description>&lt;p&gt;Accessibility is part of the composition, not a final layer of polish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduce guesswork
&lt;/h2&gt;

&lt;p&gt;Good focus states, meaningful labels, readable contrast, and predictable navigation give more people a clear path through the same experience.&lt;br&gt;
They also make the interface easier for everyone to understand when attention is divided, the room is bright, the connection is slow, or the device is unfamiliar.&lt;/p&gt;

&lt;p&gt;I think about accessibility as a series of invitations.&lt;br&gt;
A visible focus state says, "you are here."&lt;br&gt;
A useful label says, "this is what will happen."&lt;br&gt;
A clear error message says, "you can recover from this."&lt;br&gt;
Those details reduce guesswork, which is one of the most expensive forms of friction in an interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the character
&lt;/h2&gt;

&lt;p&gt;The work is rarely about choosing between a distinctive visual language and an accessible one.&lt;br&gt;
It is usually about being more deliberate with the language: using contrast as structure, typography as hierarchy, and motion only when it helps someone understand a change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the experience directly
&lt;/h2&gt;

&lt;p&gt;The best implementation is often the one that feels invisible because it simply works.&lt;br&gt;
I still check the details directly with a keyboard, a screen reader, and reduced-motion settings because assumptions are not a substitute for experiencing the interface differently.&lt;/p&gt;

&lt;p&gt;The best implementation is often the one that feels invisible because it simply works.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>ux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A Cross-Platform Terminal Setup Built on a Shared Mental Model</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 08 Sep 2026 16:45:22 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/a-cross-platform-terminal-setup-built-on-a-shared-mental-model-195p</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/a-cross-platform-terminal-setup-built-on-a-shared-mental-model-195p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I use a Windows laptop at work and a MacBook at home.&lt;br&gt;
I do not need their terminals to behave identically, but I need them to organize my work in the same way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My work laptop uses &lt;code&gt;WezTerm&lt;/code&gt; with &lt;code&gt;PowerShell&lt;/code&gt; or &lt;code&gt;Nushell&lt;/code&gt;.&lt;br&gt;
My MacBook uses &lt;code&gt;Ghostty&lt;/code&gt; with &lt;code&gt;tmux&lt;/code&gt; and either &lt;code&gt;zsh&lt;/code&gt; or &lt;code&gt;Nushell&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;Neovim&lt;/code&gt; is my editor on both machines.&lt;/p&gt;

&lt;p&gt;Making every shortcut identical would require extra configuration without solving the main problem.&lt;br&gt;
What matters is giving projects, tasks, and commands predictable places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tools can differ as long as the workflow remains familiar.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  One structure across both machines
&lt;/h2&gt;

&lt;p&gt;I organize my terminal workspace around three levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project workspace&lt;/td&gt;
&lt;td&gt;Keep related work together&lt;/td&gt;
&lt;td&gt;application project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Window or tab&lt;/td&gt;
&lt;td&gt;Separate tasks and contexts&lt;/td&gt;
&lt;td&gt;editor, Git, server, logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pane&lt;/td&gt;
&lt;td&gt;Show related commands at the same time&lt;/td&gt;
&lt;td&gt;server beside test output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the Mac, tmux adds a session around this structure.&lt;br&gt;
Each session contains windows, and each window contains one or more panes.&lt;br&gt;
A session can be detached and reattached while its programs continue running.&lt;/p&gt;

&lt;p&gt;On Windows, WezTerm handles tabs and panes directly.&lt;br&gt;
Although the tools differ, I can organize both environments using the same hierarchy.&lt;/p&gt;

&lt;p&gt;A typical project session on the Mac begins with:&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="nb"&gt;cd &lt;/span&gt;path/to/project
tmux new-session &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; project
nvim &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, I can keep the editor in one window, Git commands in another, the development server in a third, and tests or logs in a fourth.&lt;/p&gt;

&lt;p&gt;I reserve panes for commands that belong to the same immediate task.&lt;br&gt;
A server and its logs make sense together, while an editor and an unrelated deployment session usually deserve separate windows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Working rule:&lt;/strong&gt; Use a window for a different context.&lt;br&gt;
Use a pane when two views need to remain visible together.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Give each tool one clear responsibility
&lt;/h2&gt;

&lt;p&gt;This structure stays manageable because each tool has a focused role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ghostty and WezTerm&lt;/strong&gt; provide the terminal interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tmux&lt;/strong&gt; manages detachable sessions, windows, and panes on the Mac.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PowerShell, Nushell, and zsh&lt;/strong&gt; provide the command-line environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neovim&lt;/strong&gt; handles code editing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These boundaries also make problems easier to diagnose.&lt;br&gt;
The terminal, multiplexer, and shell are separate layers, so a failure in one does not necessarily mean the others are broken.&lt;/p&gt;

&lt;p&gt;Understanding which layer owns a behavior is more useful than accumulating fixes across configuration files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match actions instead of keystrokes
&lt;/h2&gt;

&lt;p&gt;Switching between operating systems was challenging at first.&lt;br&gt;
The turning point was applying the same principle to shortcuts: keep the actions consistent even when the key sequences differ.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Windows with WezTerm&lt;/th&gt;
&lt;th&gt;Mac with tmux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Split right&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;|&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split down&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;-&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move between panes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Alt-Arrow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;h/j/k/l&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resize a pane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-Arrow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;H/J/K/L&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create a window or tab&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;c&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close a pane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;x&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reload the configuration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl-b&lt;/code&gt;, then &lt;code&gt;r&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The physical shortcuts differ, but their purposes remain consistent.&lt;br&gt;
I create a window when I need another task context, split a pane when I need a related view, and resize the layout when it no longer fits the work.&lt;/p&gt;

&lt;p&gt;tmux uses a prefix model, so most commands begin with &lt;code&gt;Ctrl-b&lt;/code&gt;.&lt;br&gt;
This approach is more dependable than forcing every WezTerm shortcut through Ghostty and into tmux.&lt;br&gt;
Some direct key combinations depend on how the outer terminal encodes and forwards them, which makes exact shortcut parity fragile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A reliable two-step shortcut is better than a clever shortcut that works with only one combination of terminal, shell, and configuration.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;My setup respects the differences between Windows and macOS while giving both environments a familiar structure.&lt;br&gt;
I can move between machines and focus on the work because the editor, server, tests, and logs still have predictable places.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Standardize the mental model first.&lt;br&gt;
Standardize individual shortcuts only when they remain reliable.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>terminal</category>
      <category>productivity</category>
      <category>development</category>
      <category>backend</category>
    </item>
    <item>
      <title>AI Made Coding Faster. Now the Bottleneck Has Moved.</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Tue, 08 Sep 2026 15:41:17 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/ai-made-coding-faster-now-the-bottleneck-has-moved-4ek4</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/ai-made-coding-faster-now-the-bottleneck-has-moved-4ek4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The code is being produced faster than I can confidently review, validate, and ship it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Old workflow vs. new workflow
&lt;/h2&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%2Feijr42ly8ozxlq7l0oj6.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%2Feijr42ly8ozxlq7l0oj6.png" alt="newvsold" width="603" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest change in my workflow is not simply that AI writes code faster. It is that my role is gradually moving away from manually implementing every detail and toward &lt;strong&gt;designing, orchestrating, reviewing, and validating the overall result&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That sounds like a small shift, but it changes where I spend most of my engineering effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding is no longer the slowest part
&lt;/h2&gt;

&lt;p&gt;Working with coding agents has changed how I think about development productivity. I can define a task, let an agent explore the codebase, implement the change, add tests, and return a working diff much faster than I could build everything manually.&lt;/p&gt;

&lt;p&gt;But implementation is only one stage of software delivery.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Requirement] --&amp;gt; B[Design] --&amp;gt; C[Implementation] --&amp;gt; D[Review] --&amp;gt; E[Test] --&amp;gt; F[Deploy]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;AI can compress the implementation stage dramatically, but review, testing, integration, security, and deployment still have their own limits. When those stages cannot keep up, faster coding does not remove the bottleneck. It simply moves it downstream.&lt;/p&gt;

&lt;p&gt;This is also the point Red Hat recently raised in &lt;a href="https://www.redhat.com/en/blog/why-faster-coding-isnt-making-delivery-any-faster" rel="noopener noreferrer"&gt;Why faster coding isn't making delivery any faster&lt;/a&gt;: generating code and delivering reliable software are not the same thing.&lt;/p&gt;

&lt;p&gt;I have started to notice this more clearly in my own workflow. An agent can produce a fairly large change while I am still building the mental model needed to judge whether that change is actually good.&lt;/p&gt;

&lt;h2&gt;
  
  
  More code is not the same as more productivity
&lt;/h2&gt;

&lt;p&gt;Suppose I used to complete two meaningful changes in a day and AI now helps me produce six. Calling that a 3x productivity increase sounds reasonable at first, but only if the rest of the engineering system can absorb those six changes.&lt;/p&gt;

&lt;p&gt;They still need to be understood, reviewed, tested, integrated, and eventually operated in production. If review capacity or CI becomes the constraint, I have not created three times more value. I have created more work waiting to be validated.&lt;/p&gt;

&lt;p&gt;This is why metrics such as lines of code, commits, or even pull requests become less useful in an AI-assisted workflow. AI can increase all of them very easily without necessarily improving reliability or delivery speed.&lt;/p&gt;

&lt;p&gt;A question I find more useful is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How quickly can an idea become reliable software in production?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shifts the focus from output to outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  My workflow is shifting from writing to validating
&lt;/h2&gt;

&lt;p&gt;This is probably the biggest change I notice personally.&lt;/p&gt;

&lt;p&gt;When I write code manually, I build context as I go. I know why a function exists, why I chose one abstraction over another, and which trade-offs I made along the way.&lt;/p&gt;

&lt;p&gt;With an agent, the implementation can arrive before I have built that same mental model.&lt;/p&gt;

&lt;p&gt;So instead of spending most of my time asking &lt;em&gt;How should I implement this?&lt;/em&gt;, I increasingly find myself asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this approach fit the architecture?&lt;/li&gt;
&lt;li&gt;Did the agent reuse the right abstractions?&lt;/li&gt;
&lt;li&gt;Are the tests validating the behavior that actually matters?&lt;/li&gt;
&lt;li&gt;Did the change introduce unnecessary coupling?&lt;/li&gt;
&lt;li&gt;Is this something I would still want to maintain six months from now?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My workflow is starting to look less like:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Design] --&amp;gt; B[Write] --&amp;gt; C[Debug] --&amp;gt; D[Review]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;and more like:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Design] --&amp;gt; B[Delegate] --&amp;gt; C[Validate] --&amp;gt; D[Integrate] --&amp;gt; E[Observe]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;I do not see this as engineering becoming less important.&lt;/p&gt;

&lt;p&gt;I see it as &lt;strong&gt;engineering judgment becoming more important than code production itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The agent can produce an implementation quickly. I am still responsible for deciding whether that implementation belongs in the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster agents need stronger guardrails
&lt;/h2&gt;

&lt;p&gt;The more autonomy I give agents, the more obvious another requirement becomes: the repository needs to communicate its rules clearly.&lt;/p&gt;

&lt;p&gt;An agent can generate perfectly valid code while still making a poor engineering decision. It might duplicate business logic, bypass an existing abstraction, introduce an unnecessary dependency, cross a package boundary, or create coupling that becomes painful later.&lt;/p&gt;

&lt;p&gt;Some of those problems will be caught by tests. Others will not.&lt;/p&gt;

&lt;p&gt;That is why I increasingly see things such as &lt;code&gt;AGENTS.md&lt;/code&gt;, architecture boundaries, dependency rules, contract tests, static analysis, security checks, and CI/CD validation as part of the agentic development workflow rather than just supporting tooling.&lt;/p&gt;

&lt;p&gt;The goal is not to add more process. It is to make predictable engineering constraints &lt;strong&gt;machine-verifiable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If an agent can produce a bad implementation in seconds, I want the system to reject obvious problems just as quickly.&lt;/p&gt;

&lt;p&gt;That leaves human attention for the things that still require judgment: architecture, trade-offs, product context, maintainability, and whether we are solving the right problem in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part comes after the agent writes the code
&lt;/h2&gt;

&lt;p&gt;A lot of attention today goes toward which coding agent produces the best code or completes a task fastest.&lt;/p&gt;

&lt;p&gt;I think that matters, but I am becoming more interested in a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happens after the implementation is generated?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Can automated checks validate it? Can another agent review the diff? Can CI enforce architectural boundaries? Can security tooling identify risky changes? Can production observability tell us whether the feature actually behaves as expected?&lt;/p&gt;

&lt;p&gt;A more mature workflow starts to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Human Defines Intent]
    --&amp;gt; B[Agent Implements]
    --&amp;gt; C[Automated Validation]
    --&amp;gt; D[AI + Human Review]
    --&amp;gt; E[CI/CD]
    --&amp;gt; F[Production]
    --&amp;gt; G[Observe &amp;amp; Improve]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is where I think the larger productivity opportunity is.&lt;/p&gt;

&lt;p&gt;Not just making the coding agent faster, but designing an engineering system that can safely absorb the speed the agent creates.&lt;/p&gt;

&lt;p&gt;That shift is already changing how I work. I spend less time manually producing every implementation detail and more time &lt;strong&gt;designing the approach, orchestrating agents, validating decisions, and improving the system around them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI has made writing code faster.&lt;/p&gt;

&lt;p&gt;Now the rest of the engineering workflow has to catch up.&lt;/p&gt;

&lt;p&gt;Because the goal was never to generate more code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal is to ship better software, with confidence, faster.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Inspired by Red Hat's &lt;a href="https://www.redhat.com/en/blog/why-faster-coding-isnt-making-delivery-any-faster" rel="noopener noreferrer"&gt;Why faster coding isn't making delivery any faster&lt;/a&gt; and my own experience working with increasingly agentic development workflows.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>systemdesign</category>
      <category>programming</category>
    </item>
    <item>
      <title>Deploy Grafana to Azure with Azure CLI : From Local Docker to Durable Dashboards</title>
      <dc:creator>Khanh Nguyen</dc:creator>
      <pubDate>Wed, 12 Aug 2026 20:04:12 +0000</pubDate>
      <link>https://dev.to/khanh_nguyen_04b1205f5a91/deploy-grafana-to-azure-with-azure-cli-from-local-docker-to-durable-dashboards-5f0l</link>
      <guid>https://dev.to/khanh_nguyen_04b1205f5a91/deploy-grafana-to-azure-with-azure-cli-from-local-docker-to-durable-dashboards-5f0l</guid>
      <description>&lt;p&gt;Grafana turns data from sources such as PostgreSQL, Prometheus, and Azure Monitor into shared dashboards, charts, and alerts. While it queries that operational data, it also needs to persist its own dashboards, users, alert rules, and data-source configuration.&lt;/p&gt;

&lt;p&gt;I usually use IaC for shared Azure environments. For this deployment, I used Azure CLI first to understand and validate each dependency directly: private networking, PostgreSQL persistence, ACR image pulls, managed identity, and Container Apps secrets.&lt;/p&gt;

&lt;p&gt;This guide deploys Grafana on Azure Container Apps with Azure Database for PostgreSQL Flexible Server and a pinned image in ACR. By the end, you will have managed HTTPS ingress, private database connectivity, persistent Grafana state, and no registry password in the deployment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a production-minded lab: no &lt;code&gt;latest&lt;/code&gt; tag, anonymous access disabled, and no database password baked into the image. After validating the design, capture it in Bicep, Terraform, or your preferred IaC tool for repeatable deployments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The architecture: separate what changes from what must persist
&lt;/h2&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%2Fuw0ecvx95rksvq6ywbeb.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%2Fuw0ecvx95rksvq6ywbeb.png" alt="pic_1" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The service responsibilities are deliberately separate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Azure Container Apps&lt;/td&gt;
&lt;td&gt;Runs Grafana, provides HTTPS ingress, and manages revisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Database for PostgreSQL&lt;/td&gt;
&lt;td&gt;Stores Grafana's users, dashboards, alerting configuration, and settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACR&lt;/td&gt;
&lt;td&gt;Provides a private, deployable copy of the Grafana image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-assigned managed identity&lt;/td&gt;
&lt;td&gt;Lets Container Apps pull from ACR without registry credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Grafana's &lt;strong&gt;internal database&lt;/strong&gt; is different from the reporting database that dashboards query. This distinction is easy to miss: the internal database preserves Grafana itself, while data sources provide the metrics and business data displayed in a dashboard. Give Grafana a dedicated database and role; give dashboards a separate, read-only data-source account where possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start: prepare the Azure CLI session
&lt;/h2&gt;

&lt;p&gt;You need an Azure subscription, Docker, and the Azure CLI. The commands use Bash or zsh; use a shell such as Azure Cloud Shell, macOS Terminal, or WSL.&lt;/p&gt;

&lt;p&gt;Sign in and install the Container Apps extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login
az extension add &lt;span class="nt"&gt;--name&lt;/span&gt; containerapp &lt;span class="nt"&gt;--upgrade&lt;/span&gt;

az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.App
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.OperationalInsights
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.ContainerService
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.ContainerRegistry
az provider register &lt;span class="nt"&gt;--namespace&lt;/span&gt; Microsoft.DBforPostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set names that are unique where Azure requires them. Keep secrets out of your shell history and Git repository.&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;RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rg-grafana-dev
&lt;span class="nv"&gt;LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;canadacentral

&lt;span class="nv"&gt;VNET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vnet-grafana-dev
&lt;span class="nv"&gt;ACA_SUBNET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;snet-containerapps
&lt;span class="nv"&gt;POSTGRES_SUBNET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;snet-postgres
&lt;span class="nv"&gt;PRIVATE_DNS_ZONE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grafana.private.postgres.database.azure.com

&lt;span class="nv"&gt;POSTGRES_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pg-grafana-dev
&lt;span class="nv"&gt;POSTGRES_ADMIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pgadmin
&lt;span class="nv"&gt;GRAFANA_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grafana
&lt;span class="nv"&gt;GRAFANA_DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grafana_app

&lt;span class="nv"&gt;ACR_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;globally-unique-acr-name&amp;gt;
&lt;span class="nv"&gt;IDENTITY_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;id-grafana-acr-pull
&lt;span class="nv"&gt;ACA_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cae-grafana-dev
&lt;span class="nv"&gt;APP_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grafana

&lt;span class="c"&gt;# Pin the version that you tested. Do not deploy :latest.&lt;/span&gt;
&lt;span class="nv"&gt;GRAFANA_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;13.1.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Collect secret values interactively. Replace these with Azure Key Vault references for a shared or production environment.&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="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rsp&lt;/span&gt; &lt;span class="s2"&gt;"PostgreSQL admin password: "&lt;/span&gt; POSTGRES_ADMIN_PASSWORD&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo
read&lt;/span&gt; &lt;span class="nt"&gt;-rsp&lt;/span&gt; &lt;span class="s2"&gt;"Grafana database password: "&lt;/span&gt; GRAFANA_DB_PASSWORD&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo
read&lt;/span&gt; &lt;span class="nt"&gt;-rsp&lt;/span&gt; &lt;span class="s2"&gt;"Grafana admin password: "&lt;/span&gt; GRAFANA_ADMIN_PASSWORD&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo
&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 48&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The server name, ACR name, and PostgreSQL administrator shown above are examples. Do not reuse the sample passwords or enable anonymous access from a local experiment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Optional: validate Grafana locally
&lt;/h2&gt;

&lt;p&gt;This optional check proves the image and database configuration before Azure resources are created. If PostgreSQL runs on your host, Docker Desktop exposes it as &lt;code&gt;host.docker.internal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a local-only &lt;code&gt;.env&lt;/code&gt; file. Never commit it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GF_DATABASE_TYPE=postgres
GF_DATABASE_HOST=host.docker.internal:5432
GF_DATABASE_NAME=grafana
GF_DATABASE_USER=grafana_app
GF_DATABASE_PASSWORD=replace-me
GF_DATABASE_SSL_MODE=disable
GF_SECURITY_ADMIN_PASSWORD=replace-me
GF_SECURITY_SECRET_KEY=replace-with-a-long-random-value
GF_AUTH_ANONYMOUS_ENABLED=false
GF_PANELS_DISABLE_SANITIZE_HTML=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the pinned image and check its health endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull &lt;span class="s2"&gt;"grafana/grafana:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
docker run &lt;span class="nt"&gt;--detach&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; grafana &lt;span class="nt"&gt;--publish&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env &lt;span class="s2"&gt;"grafana/grafana:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

curl &lt;span class="nt"&gt;--fail&lt;/span&gt; http://localhost:3000/api/health
docker logs grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux Docker Engine, add &lt;code&gt;--add-host=host.docker.internal:host-gateway&lt;/code&gt; if needed. This hostname is local-only; Azure uses the PostgreSQL server FQDN.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Give Grafana durable, private state
&lt;/h2&gt;

&lt;p&gt;Create the resource group, VNet, and two dedicated subnets. The Container Apps environment requires its own subnet. PostgreSQL Flexible Server requires a separate subnet delegated to &lt;code&gt;Microsoft.DBforPostgreSQL/flexibleServers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

az network vnet create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--address-prefixes&lt;/span&gt; 10.0.0.0/16

az network vnet subnet create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--vnet-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACA_SUBNET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--address-prefixes&lt;/span&gt; 10.0.0.0/21 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delegations&lt;/span&gt; Microsoft.App/environments

az network vnet subnet create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--vnet-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_SUBNET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--address-prefixes&lt;/span&gt; 10.0.8.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delegations&lt;/span&gt; Microsoft.DBforPostgreSQL/flexibleServers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create and link the private DNS zone. The link allows workloads in the VNet to resolve the database server to its private address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az network private-dns zone create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIVATE_DNS_ZONE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

az network private-dns &lt;span class="nb"&gt;link &lt;/span&gt;vnet create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--zone-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIVATE_DNS_ZONE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; grafana-vnet-link &lt;span class="nt"&gt;--virtual-network&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--registration-enabled&lt;/span&gt; &lt;span class="nb"&gt;false

&lt;/span&gt;&lt;span class="nv"&gt;PRIVATE_DNS_ZONE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az network private-dns zone show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIVATE_DNS_ZONE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create PostgreSQL and the Grafana database. PostgreSQL 18 is used here because it is currently supported by Flexible Server; choose a supported version approved by your organization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az postgres flexible-server create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--admin-user&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_ADMIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--admin-password&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; 18 &lt;span class="nt"&gt;--tier&lt;/span&gt; Burstable &lt;span class="nt"&gt;--sku-name&lt;/span&gt; Standard_B1ms &lt;span class="nt"&gt;--storage-size&lt;/span&gt; 32 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vnet&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--subnet&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_SUBNET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--private-dns-zone&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIVATE_DNS_ZONE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

az postgres flexible-server db create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--server-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;POSTGRES_FQDN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az postgres flexible-server show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; fullyQualifiedDomainName &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a least-privileged Grafana role
&lt;/h3&gt;

&lt;p&gt;For a private database, run the following from a trusted machine that has network access to the VNet, such as a jump host. It creates an application role and makes it owner of the Grafana database, which lets Grafana run its own schema migrations without using the server administrator at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="s2"&gt;"host=&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_FQDN&lt;/span&gt;&lt;span class="s2"&gt; port=5432 dbname=postgres user=&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_ADMIN&lt;/span&gt;&lt;span class="s2"&gt; sslmode=require"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;db_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;db_user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_DB_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;grafana_password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_DB_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&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;SQL&lt;/span&gt;&lt;span class="sh"&gt;'
SELECT format('CREATE ROLE %I LOGIN PASSWORD %L', :'db_user', :'grafana_password') &lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;&lt;span class="sh"&gt;exec
SELECT format('ALTER DATABASE %I OWNER TO %I', :'db_name', :'db_user') &lt;/span&gt;&lt;span class="se"&gt;\g&lt;/span&gt;&lt;span class="sh"&gt;exec
&lt;/span&gt;&lt;span class="no"&gt;SQL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The FQDN is read from the provisioned server instead of assumed. In a production environment, manage this database setup through a reviewed migration or infrastructure workflow instead of typing credentials into an interactive terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Prepare a private image and passwordless pull access
&lt;/h2&gt;

&lt;p&gt;ACR can import the public image directly, so your deployment does not depend on Docker Hub at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az acr create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--sku&lt;/span&gt; Basic &lt;span class="nt"&gt;--role-assignment-mode&lt;/span&gt; rbac

&lt;span class="c"&gt;# Required for managed-identity pulls by Azure Container Apps.&lt;/span&gt;
az acr config authentication-as-arm update &lt;span class="nt"&gt;--registry&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--status&lt;/span&gt; enabled

&lt;span class="nv"&gt;ACR_LOGIN_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az acr show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; loginServer &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;

az acr import &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="s2"&gt;"docker.io/grafana/grafana:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_VERSION&lt;/span&gt;&lt;span class="k"&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;--image&lt;/span&gt; &lt;span class="s2"&gt;"grafana:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pinning &lt;code&gt;13.1.3&lt;/code&gt; avoids the ambiguity of &lt;code&gt;latest&lt;/code&gt;. Tags can still be moved, so for an immutable production release, record and deploy the imported image digest. When you upgrade, test the new version, import it, and deploy it as a new Container Apps revision.&lt;/p&gt;

&lt;p&gt;Create a user-assigned managed identity and grant it pull-only access to this registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az identity create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDENTITY_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;IDENTITY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az identity show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDENTITY_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;IDENTITY_PRINCIPAL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az identity show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDENTITY_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; principalId &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ACR_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az acr show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;

az role assignment create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assignee-object-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDENTITY_PRINCIPAL_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assignee-principal-type&lt;/span&gt; ServicePrincipal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; AcrPull &lt;span class="nt"&gt;--scope&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your registry uses the newer RBAC + ABAC repository-permission mode, use &lt;strong&gt;Container Registry Repository Reader&lt;/strong&gt; instead of &lt;code&gt;AcrPull&lt;/code&gt;, ideally scoped to the &lt;code&gt;grafana&lt;/code&gt; repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Run Grafana with secrets outside the image
&lt;/h2&gt;

&lt;p&gt;Retrieve the Container Apps subnet ID, then attach it when you create the environment.&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;ACA_SUBNET_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az network vnet subnet show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--vnet-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNET_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACA_SUBNET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;

az containerapp &lt;span class="nb"&gt;env &lt;/span&gt;create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACA_ENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--infrastructure-subnet-resource-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACA_SUBNET_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the app with external HTTPS ingress. Secrets are stored by Container Apps and referenced with &lt;code&gt;secretref:&lt;/code&gt;. They are not image labels, plain environment-variable values in source control, or ACR credentials. This lab passes secret values through the CLI; use Key Vault references for production to avoid exposing secret values to the local process environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--environment&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACA_ENV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ACR_LOGIN_SERVER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/grafana:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_VERSION&lt;/span&gt;&lt;span class="k"&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;--user-assigned&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDENTITY_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--registry-server&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACR_LOGIN_SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--registry-identity&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IDENTITY_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ingress&lt;/span&gt; external &lt;span class="nt"&gt;--target-port&lt;/span&gt; 3000 &lt;span class="nt"&gt;--transport&lt;/span&gt; auto &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpu&lt;/span&gt; 0.5 &lt;span class="nt"&gt;--memory&lt;/span&gt; 1Gi &lt;span class="nt"&gt;--min-replicas&lt;/span&gt; 1 &lt;span class="nt"&gt;--max-replicas&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secrets&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    grafana-db-password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_DB_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    grafana-admin-password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    grafana-secret-key&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GRAFANA_SECRET_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-vars&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_DATABASE_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"GF_DATABASE_HOST=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_FQDN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:5432"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"GF_DATABASE_NAME=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"GF_DATABASE_USER=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_DB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_DATABASE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secretref:grafana-db-password &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_DATABASE_SSL_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;require &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_SECURITY_ADMIN_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secretref:grafana-admin-password &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_SECURITY_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secretref:grafana-secret-key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_AUTH_ANONYMOUS_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;GF_PANELS_DISABLE_SANITIZE_HTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app is intentionally fixed at one replica. Grafana's application state lives in PostgreSQL, but a multi-replica Grafana design still needs deliberate consideration for plugins, sessions, provisioning, alerting, and load balancing. Start with one replica; scale only after validating those behaviours for your Grafana version and plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Confirm the deployment and persistence
&lt;/h2&gt;

&lt;p&gt;Retrieve the endpoint and open it in a browser:&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;GRAFANA_FQDN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az containerapp show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; properties.configuration.ingress.fqdn &lt;span class="nt"&gt;--output&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GRAFANA_FQDN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in with &lt;code&gt;admin&lt;/code&gt; and the password supplied as &lt;code&gt;GRAFANA_ADMIN_PASSWORD&lt;/code&gt; on Grafana's first start. Grafana applies &lt;code&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/code&gt; only when it creates the initial admin user; later changes require Grafana's supported password-management path. If the revision does not become healthy, tail the container logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az containerapp logs show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; console &lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most common failures are straightforward:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;First check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Image pull fails&lt;/td&gt;
&lt;td&gt;Identity role has not propagated or has the wrong ACR role&lt;/td&gt;
&lt;td&gt;Verify the role assignment and wait briefly before retrying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;no such host&lt;/code&gt; / DB connection fails&lt;/td&gt;
&lt;td&gt;Private DNS zone is not linked or the FQDN is wrong&lt;/td&gt;
&lt;td&gt;Confirm the zone link and use the server's Azure FQDN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS or authentication failure&lt;/td&gt;
&lt;td&gt;Database SSL or credentials are incorrect&lt;/td&gt;
&lt;td&gt;Keep &lt;code&gt;GF_DATABASE_SSL_MODE=require&lt;/code&gt; and rotate/reapply the secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana starts but settings disappear&lt;/td&gt;
&lt;td&gt;Grafana is using SQLite or the wrong database&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;GF_DATABASE_*&lt;/code&gt; variables and startup logs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a quick persistence check, create a throwaway dashboard, trigger a new Container Apps revision with the same image, and confirm that the dashboard remains after the revision becomes active. If it disappears, stop and fix the database configuration before inviting users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production next steps
&lt;/h2&gt;

&lt;p&gt;The CLI workflow makes the Azure relationships visible. Before using it for a shared production environment, codify the deployment as IaC and add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Key Vault references and a secret-rotation process.&lt;/li&gt;
&lt;li&gt;Microsoft Entra ID, a custom domain, and restricted ingress or a WAF.&lt;/li&gt;
&lt;li&gt;PostgreSQL backups, a restore test, and a production-appropriate compute tier. Consider &lt;code&gt;verify-full&lt;/code&gt; for the Grafana-to-PostgreSQL TLS connection after validating the container's CA trust.&lt;/li&gt;
&lt;li&gt;Diagnostics and alerts for revision failures, database capacity, and authentication issues.&lt;/li&gt;
&lt;li&gt;Versioned Grafana provisioning, plugin governance, and tested image upgrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Clean up
&lt;/h2&gt;

&lt;p&gt;For a disposable lab, delete the resource group after confirming it contains no shared resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group delete &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--no-wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletion stops the ongoing costs for resources in that group. Check your subscription afterward for any separately scoped diagnostics, backups, or retained resources you intentionally created.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/" rel="noopener noreferrer"&gt;Grafana configuration documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/" rel="noopener noreferrer"&gt;Run Grafana in Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/vnet-custom" rel="noopener noreferrer"&gt;Azure Container Apps virtual network integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/manage-secrets" rel="noopener noreferrer"&gt;Azure Container Apps secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/managed-identity-image-pull" rel="noopener noreferrer"&gt;Managed identity image pulls for Container Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/postgresql/network/concepts-networking-private" rel="noopener noreferrer"&gt;Azure Database for PostgreSQL private access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-registry/container-registry-rbac-built-in-roles-overview" rel="noopener noreferrer"&gt;ACR roles and ABAC repository permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>cli</category>
      <category>grafana</category>
    </item>
  </channel>
</rss>
