<?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: erfankashani</title>
    <description>The latest articles on DEV Community by erfankashani (@erfankashani).</description>
    <link>https://dev.to/erfankashani</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%2F227877%2F46eb2331-30ba-491a-93f1-08f3c7696295.jpeg</url>
      <title>DEV Community: erfankashani</title>
      <link>https://dev.to/erfankashani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erfankashani"/>
    <language>en</language>
    <item>
      <title>Model Migration on Productionized AI Applications: What Changes Beyond the API</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Sat, 03 Oct 2026 04:20:42 +0000</pubDate>
      <link>https://dev.to/erfankashani/model-migration-on-productionized-ai-applications-what-changes-beyond-the-api-1oi8</link>
      <guid>https://dev.to/erfankashani/model-migration-on-productionized-ai-applications-what-changes-beyond-the-api-1oi8</guid>
      <description>&lt;p&gt;Google is retiring &lt;code&gt;gemini-2.5-pro&lt;/code&gt; on Vertex AI on &lt;strong&gt;October 20, 2026&lt;/strong&gt;. If your product runs on it, you need to migrate before then!&lt;/p&gt;

&lt;p&gt;I recently went through this migration practice on a legal document extraction AI-native product that runs on production. I knew going in that, it would be more than a config change, but I did not expect how many layers it would touch. &lt;/p&gt;

&lt;p&gt;Changing the model in an AI product is very different from pointing a client at a new endpoint or upgrading a library. Each model has its own strengths and weaknesses, and its own way of filling the gaps when the input is not clear. When you swap the model, your product starts behaving differently. If you do not have the right harnesses, evals, and monitoring in place, you find out through accuracy drops, and the worst of them show up exactly where you were counting on the model's thinking capability on inferring data.&lt;/p&gt;

&lt;p&gt;Below are the issues I ran into, in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Where the model lives
&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%2Fljkl6qxe6agjkns8ajc5.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%2Fljkl6qxe6agjkns8ajc5.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the most consequential decisions you have to make (not as a developer but as an organization) is where the model is being provisioned and how this impacts your data residency requirements. Gemini 2.5 Pro could be called from a single region, such as &lt;code&gt;us-central1&lt;/code&gt;. Gemini 3.8 Flash cannot. It is offered only as a multi-region deployment (&lt;code&gt;us&lt;/code&gt; or &lt;code&gt;eu&lt;/code&gt;) or a global deployment, and each one uses a different endpoint shape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Deployment type&lt;/th&gt;
&lt;th&gt;Example location&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single-region&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us-central1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{region}-aiplatform.googleapis.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-region&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;us&lt;/code&gt;, &lt;code&gt;eu&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aiplatform.{loc}.rep.googleapis.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;&lt;code&gt;global&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aiplatform.googleapis.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This difference is significant for a multinational company. A regional deployment gave you a clear answer to "where is our customer data processed?" Multi-region still keeps processing inside a jurisdiction such as the US or the EU. Global routes each request to wherever Google has capacity, so there is no data residency guarantee. If your contracts, regulators, or compliance team care about residency, you need to discuss the model location with them before you change it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The caveats of moving to multi-regional endpoint
&lt;/h3&gt;

&lt;p&gt;If your application is deployed on a cloud run with private VPC sharing and choose to move forward with a multi-region deployment option, There are chances that your first call fails with the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch,
certificate is not valid for 'aiplatform.us.rep.googleapis.com'. (_ssl.c:1016)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is fine but the networking rules.. not so much. The Cloud Run service sent its egress through a centrally managed Shared VPC subnet with Private Google Access enabled. That setup works for regional and global endpoints, but not for multi-region ones. Multi-region endpoints only became generally available on May 15, 2026, so many existing network setups were never designed with them in mind. Google documents the limitation in About accessing the Vertex AI API:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Private Google Access isn't supported for multi-region endpoints. If you attempt to connect to a multi-region endpoint using Private Google Access, you might experience connectivity issues, SSL/TLS handshake errors, or certificate mismatch warnings.&lt;/p&gt;

&lt;p&gt;To establish private connectivity to multi-region endpoints, you must configure Private Service Connect endpoints for regional Google APIs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The proper fix is a &lt;a href="https://docs.cloud.google.com/vpc/docs/access-regional-google-apis-endpoints" rel="noopener noreferrer"&gt;Private Service Connect endpoint&lt;/a&gt; for the regional Google APIs. When a central team owns networking, that is a medium-to-large change with its own timeline.&lt;/p&gt;

&lt;p&gt;An interim workaround is to use the &lt;code&gt;global&lt;/code&gt; deployment, which its endpoint works with the existing network. It has two trade-offs you should know up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no data residency guarantee, for the reasons above.&lt;/li&gt;
&lt;li&gt;There is no context caching. The global endpoint does not support it, so make sure your code is not relying on the caching mechanism or if it does currently, you have implemented fail-safe approaches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are planning this migration, check your egress path before you write any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: The API contract changed
&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%2F4b5805qkpd7av78jwtbz.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%2F4b5805qkpd7av78jwtbz.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The syntax for passing &lt;code&gt;tools&lt;/code&gt; is the same between the two models, which makes it easy to assume nothing else changed. Several things did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thinking configuration.&lt;/strong&gt; Gemini 2.5 Pro takes a numeric &lt;code&gt;thinking_budget&lt;/code&gt;. Gemini 3.8 Flash rejects it and expects a &lt;code&gt;thinking_level&lt;/code&gt; enum: &lt;code&gt;LOW&lt;/code&gt;, &lt;code&gt;MEDIUM&lt;/code&gt; (default), or &lt;code&gt;HIGH&lt;/code&gt;. &lt;code&gt;MINIMAL&lt;/code&gt; is not supported on 3.8 Flash and fails API validation (unlike &lt;code&gt;Gemini 3.5 Flash&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sampling parameters.&lt;/strong&gt; The Gemini 3 family drops the legacy sampling options. Remove &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;top_p&lt;/code&gt;, and &lt;code&gt;top_k&lt;/code&gt; from your &lt;code&gt;GenerateContentConfig&lt;/code&gt;. &lt;code&gt;candidate_count&lt;/code&gt; is not supported at all from Gemini 3.x onward. A lot of extraction code hardcodes a low temperature to get "deterministic" output, and Google calls out that exact pattern in its &lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/start/gemini-3-prompting-guide" rel="noopener noreferrer"&gt;Gemini 3 prompting guide&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your existing code explicitly sets temperature (especially to low values for deterministic outputs), we recommend removing this parameter and using the Gemini 3 default of 1.0... Changing the temperature (setting it below 1.0) may lead to unexpected behavior, such as looping or degraded performance, particularly in complex mathematical or reasoning tasks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In code, the change looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.genai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;

&lt;span class="c1"&gt;# Before: gemini-2.5-pro
&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerateContentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top_p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;thinking_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThinkingConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thinking_budget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After: gemini-3.8-flash
&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerateContentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;thinking_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThinkingConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thinking_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MEDIUM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing the syntax is the easy part. A budget is a number you tune, but a level is a product decision. You now have to decide which flows deserve &lt;code&gt;HIGH&lt;/code&gt; and which are fine on &lt;code&gt;MEDIUM&lt;/code&gt; or &lt;code&gt;LOW&lt;/code&gt;. Each choice changes model behavior, so each one needs its own accuracy validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function calling.&lt;/strong&gt; If your application relies on them, there are three differences to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you orchestrate multi-turn tool use yourself, every &lt;code&gt;FunctionResponse&lt;/code&gt; you send back must include both a &lt;code&gt;call_id&lt;/code&gt; and a &lt;code&gt;name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If your prompt places inline instructions directly before a tool call, 2.5 Pro handles it, but 3.8 Flash can return &lt;code&gt;MALFORMED_FUNCTION_CALL&lt;/code&gt;. Separate those instructions with clean blank lines (&lt;code&gt;\n\n&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If your tools return images or audio, 3.8 Flash expects the assets inside the response payload rather than referenced through an external pointer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Multi-turn chats.&lt;/strong&gt; 3.8 Flash enforces server-side state tracking through &lt;code&gt;previous_interaction_id&lt;/code&gt;, and it restricts manually pre-filling model turns in the payload history more than 2.5 did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context caching minimum.&lt;/strong&gt; Once you are on a regional or multi-region endpoint where caching works, note that the minimum cache size goes from 1,024 tokens on 2.5 Pro to 4,096 tokens on 3.8 Flash. Short inputs, such as a single small document, will no longer trigger caching. There is no code change, but your cost profile changes, so measure it on real traffic instead of estimating it. See &lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-create" rel="noopener noreferrer"&gt;Create a context cache&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: The model thinks differently
&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%2F9eynfyhy2qdbc87gwidb.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%2F9eynfyhy2qdbc87gwidb.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This part is not in any release note, and it is where the feel the migration can change your product (or its taste).&lt;/p&gt;

&lt;p&gt;Both models are strong thinkers, but they approach the same problem differently, much like two experienced people would. Part of that difference is the Pro and Flash tiers being designed for different jobs, and part of it is the generation. From the product's point of view, the cause matters less than the effect.&lt;/p&gt;

&lt;p&gt;Gemini 2.5 Pro is the more verbose of the two. It is comfortable with long reasoning chains, and it likes to connect dots, sometimes ones that are not there. In one case it inferred a transaction date from the date of the email chain the document came from. In another, a contract's start date depended on a condition under a separate agreement being met. 2.5 Pro took the start date at face value, added the contract term, and returned a confident end date.&lt;/p&gt;

&lt;p&gt;Output format was the other difference. Our response schema is referenced in the prompt rather than enforced through function calling. With 2.5 Pro, this approach gave better results than enforcing the schema, which left us with many null records in the output. The cost was that 2.5 Pro sometimes drifted from the requested JSON shape.&lt;/p&gt;

&lt;p&gt;Gemini 3.8 Flash on &lt;code&gt;MEDIUM&lt;/code&gt; is more concise and less inclined to overthink. In the same conditional-contract case, it noticed that the start date depended on an event that had not happened yet, and it did not assume either date. When the evidence was not clear, it left the field &lt;code&gt;null&lt;/code&gt;, which is exactly what our instructions asked for. It followed the output conventions in the prompt much more cleanly, kept to the JSON shape from the prompt-referenced schema without being forced by function calling, and did not do more than it was asked.&lt;/p&gt;

&lt;p&gt;Which behavior is better depends on the product. For legal extraction, an inferred date that looks authoritative is worse than an honest &lt;code&gt;null&lt;/code&gt;. For another product, the 2.5 Pro habit of connecting dots might be exactly what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this taught me about choosing a model
&lt;/h2&gt;

&lt;p&gt;Every thinking model has its own personality. A prompt tuned for one model is not tuned for the next, and the habits you build into your prompts, such as how firmly you state the output format or how you tell the model to handle missing data, can have a different effect on another model. In my experience, even the same model can behave differently depending on which cloud serves it, since providers can wrap your input with their own system-level instructions before it reaches the model.&lt;/p&gt;

&lt;p&gt;That changes how I think about model selection. Tuning prompts and validation around a model takes real effort, so choose a model with enough lifespan to make that effort worth it. Be careful with model routers in precision work like legal document extraction, because the same document can get a different interpretation depending on which model answered it. This is also why some teams invest in smaller models they host themselves and fine-tuned for specific task.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually learn a model
&lt;/h2&gt;

&lt;p&gt;Release notes list what changed in the API, but they cannot show you how the model handles your own documents.&lt;/p&gt;

&lt;p&gt;One of the most effective ways I have found is to build an eval set for your application along with a set of standard baselines. With that in place, you can run different models, and the same model at different thinking levels, against the same tasks and compare the results directly. You can also see where each model is weak and where you can add quality through context management, prompt management, or tool calling, instead of guessing.&lt;/p&gt;

&lt;p&gt;I will write about my approach to eval testing in my next post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/model-versions" rel="noopener noreferrer"&gt;Model versions and lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/learn/locations" rel="noopener noreferrer"&gt;Deployments and endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/general/googleapi-access-methods" rel="noopener noreferrer"&gt;About accessing the Vertex AI API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/vpc/docs/access-regional-google-apis-endpoints" rel="noopener noreferrer"&gt;Accessing Vertex AI through Private Service Connect endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/start/gemini-3-prompting-guide" rel="noopener noreferrer"&gt;Gemini 3 prompting guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-create" rel="noopener noreferrer"&gt;Create a context cache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gemini</category>
      <category>aiengineer</category>
      <category>generativeai</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Moving Your Local Airflow to GCP for under $150 a month</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Mon, 28 Sep 2026 02:17:33 +0000</pubDate>
      <link>https://dev.to/erfankashani/moving-your-local-airflow-to-gcp-for-under-150-a-month-2i1j</link>
      <guid>https://dev.to/erfankashani/moving-your-local-airflow-to-gcp-for-under-150-a-month-2i1j</guid>
      <description>&lt;p&gt;My team runs ML training and prediction pipelines on Vertex AI. For a long&lt;br&gt;
time, the thing telling those pipelines when to run was Apache Airflow,&lt;br&gt;
installed by hand on a few on-prem Linux boxes. From there it orchestrated&lt;br&gt;
our hybrid cloud setup from one place.&lt;/p&gt;

&lt;p&gt;Next to Airflow sat our own configuration management system. It lets people&lt;br&gt;
change a pipeline's settings on demand, and every change is tracked and&lt;br&gt;
logged over time, so if a model behaves differently on Tuesday, we can see&lt;br&gt;
what changed on Monday.&lt;/p&gt;

&lt;p&gt;Both of these lived on Linux boxes we maintained ourselves, and those boxes&lt;br&gt;
had to go: our organization is phasing out its on-prem setup and moving fully&lt;br&gt;
cloud native. We are in the middle of that move right now. The on-prem&lt;br&gt;
pipelines are moving out to different clouds, and our own pipelines and&lt;br&gt;
compute are moving into GCP. With the work landing in GCP, putting the&lt;br&gt;
orchestrator there too was the natural answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkl220wrhgy5zfhsnz12w.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%2Fkl220wrhgy5zfhsnz12w.png" alt="migration" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal was to make that move without ending up any less safe or less&lt;br&gt;
reliable than what we had.&lt;/p&gt;

&lt;p&gt;In this post I want to share how we did it, chapter by chapter. The code is&lt;br&gt;
in the &lt;a href="https://github.com/erfankashani/self-managed-airflow-on-gcp" rel="noopener noreferrer"&gt;&lt;code&gt;self-managed-airflow-on-gcp&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
repo, and everything below is a trimmed-down, working version of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Chapter 1: Trying Cloud Composer
&lt;/h2&gt;

&lt;p&gt;If you ask "how do I run Airflow on GCP?", the answer is Cloud Composer.&lt;br&gt;
It is managed Airflow: Google runs the scheduler, the database and the&lt;br&gt;
workers, and you drop DAGs into a bucket.&lt;/p&gt;

&lt;p&gt;We built it first. We stood up a Composer 3 environment with Terraform,&lt;br&gt;
pointed our DAGs at it, and it worked.&lt;/p&gt;

&lt;p&gt;The cost was the problem. With the pricing calculator, a small Composer&lt;br&gt;
environment came to about &lt;strong&gt;$350 a month&lt;/strong&gt;. Composer 3 bills for compute&lt;br&gt;
units every hour the environment is alive, whether a DAG runs or not. Our&lt;br&gt;
pipelines do not need a cluster awake around the clock to schedule a handful&lt;br&gt;
of jobs, because the heavy work happens in Vertex AI anyway.&lt;/p&gt;

&lt;p&gt;My search was not done there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Chapter 2: Self-managed Airflow on a VM
&lt;/h2&gt;

&lt;p&gt;Our Airflow does not do the heavy work. It decides when work happens and&lt;br&gt;
hands it to Vertex AI or BigQuery. A scheduler like that fits on one small&lt;br&gt;
VM.&lt;/p&gt;

&lt;p&gt;So we built the same thing a second time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one Compute Engine VM running a self-managed &lt;strong&gt;Airflow 3.x&lt;/strong&gt;, with Postgres
for the metadata database&lt;/li&gt;
&lt;li&gt;Terraform to provision the platform, per environment (dev, test, prod)&lt;/li&gt;
&lt;li&gt;a Fabric script to install Airflow, deploy code, and handle day-to-day
maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The estimate for this came in &lt;strong&gt;under $150 a month&lt;/strong&gt;. About $20 of that is&lt;br&gt;
the HTTPS Load Balancer in front of our config app (GCP charges a flat&lt;br&gt;
$0.025/hour for the first five forwarding rules, roughly $18/month, plus&lt;br&gt;
data). The rest is the VM, its disk, and Cloud NAT.&lt;/p&gt;

&lt;p&gt;That number is not the whole cost. With Composer, Google upgrades Airflow and&lt;br&gt;
patches the machine. On a VM, we do. We had both versions running side by&lt;br&gt;
side, and the team picked the self-managed one: we are a technical team that&lt;br&gt;
already ran Airflow ourselves, so the extra work was work we knew.&lt;/p&gt;

&lt;p&gt;Cheaper only counts if it is also safe and reliable, so the rest of this post&lt;br&gt;
is about how we got there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Chapter 3: How the pieces fit
&lt;/h2&gt;

&lt;p&gt;There are two components we deploy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;config_hq&lt;/code&gt;&lt;/strong&gt;, the new home of our configuration system. It is a small
Flask app with a plain HTML/JS frontend, running on Cloud Run. A user types
config, presses Save, and it is written to a GCS bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;airflow-vm&lt;/code&gt;&lt;/strong&gt;, the orchestrator. Its DAGs read that config and kick off
the ML jobs.&lt;/li&gt;
&lt;/ol&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%2Fxqkd6fd48heff7rktfza.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%2Fxqkd6fd48heff7rktfza.png" alt="architecture_diagram" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The part I like most in this design is that Airflow never calls &lt;code&gt;config_hq&lt;/code&gt;.&lt;br&gt;
The two only share a bucket.&lt;/p&gt;

&lt;p&gt;Our org policy disables Cloud Run's default &lt;code&gt;run.app&lt;/code&gt; URL, so the service is&lt;br&gt;
deployed with &lt;code&gt;default_uri_disabled = true&lt;/code&gt; and there is no hostname for a&lt;br&gt;
DAG to call anyway. Instead, &lt;code&gt;config_hq&lt;/code&gt; writes every save as a new object&lt;br&gt;
named &lt;code&gt;config/&amp;lt;timestamp&amp;gt;-&amp;lt;id&amp;gt;.txt&lt;/code&gt; in a versioned bucket. That bucket is&lt;br&gt;
also our change history. The VM gets read-only access to it, and the DAG&lt;br&gt;
reads it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_configs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GCSHook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;configs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;blob_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CONFIG_HQ_BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;blob_name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;blob_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CONFIG_HQ_BUCKET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ti&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;xcom_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;config_hq&lt;/code&gt; is down, Airflow still reads the last config that was saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 4: Security
&lt;/h2&gt;

&lt;p&gt;Both components are closed by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;config_hq&lt;/code&gt;&lt;/strong&gt; is only reachable through an External HTTPS Load Balancer&lt;br&gt;
with Identity-Aware Proxy (IAP) on the backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Run ingress is set to &lt;code&gt;INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER&lt;/code&gt;, so
the Load Balancer is the only way in.&lt;/li&gt;
&lt;li&gt;IAP only lets through Google accounts listed in &lt;code&gt;iap_authorized_members&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Its service account can write to its own bucket and nothing else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The VM&lt;/strong&gt; has no external IP at all (org policy&lt;br&gt;
&lt;code&gt;constraints/compute.vmExternalIpAccess&lt;/code&gt;), so there is no public path to it.&lt;br&gt;
Getting in means passing two separate gates:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdjldq0gl4orbbv6k9xzt.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%2Fdjldq0gl4orbbv6k9xzt.png" alt="security" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The firewall only allows port 22 (and 8080 for the Airflow UI) from
Google's IAP range, &lt;code&gt;35.235.240.0/20&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Opening the tunnel needs one IAM role, and logging in needs a second one
(OS Login).&lt;/li&gt;
&lt;li&gt;Every SSH session shows up in Cloud Audit Logs under the person who opened
it.&lt;/li&gt;
&lt;li&gt;It is a Shielded VM (Secure Boot, vTPM, integrity monitoring), required by
&lt;code&gt;constraints/compute.requireShieldedVm&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For outbound traffic, Private Google Access covers &lt;code&gt;*.googleapis.com&lt;/code&gt;, and&lt;br&gt;
Cloud NAT handles everything else the VM needs for installs and &lt;code&gt;git pull&lt;/code&gt;&lt;br&gt;
(GitHub, PyPI, and &lt;code&gt;dev.azure.com&lt;/code&gt; for the git remote).&lt;/p&gt;
&lt;h2&gt;
  
  
  Chapter 5: The ML pipeline
&lt;/h2&gt;

&lt;p&gt;The repo has one sample pipeline. It shows the same training and prediction&lt;br&gt;
flow we use for our own models.&lt;/p&gt;

&lt;p&gt;The model code lives in &lt;code&gt;app/&lt;/code&gt; as a Python package, &lt;code&gt;ml_experiment&lt;/code&gt;. It&lt;br&gt;
trains a logistic regression on BigQuery's public penguins dataset. We build&lt;br&gt;
it into a wheel, upload it to the &lt;code&gt;ml-artifacts&lt;/code&gt; bucket, and a DAG submits&lt;br&gt;
it to Vertex AI as a custom training job inside Google's prebuilt&lt;br&gt;
&lt;code&gt;sklearn-cpu.1-0&lt;/code&gt; container. Here is a full run, from saving config to the&lt;br&gt;
job finishing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcq2o5t8mjvjdih2wpvk.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%2Fwcq2o5t8mjvjdih2wpvk.png" alt="sequence_diagram" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Vertex job runs as the VM's own service account, which holds&lt;br&gt;
&lt;code&gt;roles/aiplatform.user&lt;/code&gt;, &lt;code&gt;roles/bigquery.jobUser&lt;/code&gt;, and write access to its&lt;br&gt;
staging bucket. Airflow gets its GCP credentials from the VM's metadata&lt;br&gt;
server, so there are no key files on the machine.&lt;/p&gt;
&lt;h2&gt;
  
  
  Chapter 6: Deployment
&lt;/h2&gt;

&lt;p&gt;The platform is Terraform, one folder per component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform/
├── config_hq/     # Cloud Run, Load Balancer, IAP, config bucket
└── airflow_vm/    # VM, firewall, Cloud NAT, ml-artifacts + vertex-staging buckets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one uses Terraform workspaces, so dev, test and prod get their own&lt;br&gt;
copies inside the same project, with the env name as a suffix&lt;br&gt;
(&lt;code&gt;airflow-vm-prod&lt;/code&gt;, &lt;code&gt;example-project-config-hq-prod&lt;/code&gt;). A precondition&lt;br&gt;
refuses to apply on the unnamed &lt;code&gt;default&lt;/code&gt; workspace, so nobody creates&lt;br&gt;
resources without an environment by accident.&lt;/p&gt;

&lt;p&gt;Terraform only builds a bare VM. Airflow goes on with Fabric, from&lt;br&gt;
&lt;code&gt;deployer/fabfile.py&lt;/code&gt;, over the IAP tunnel. These are the tasks we use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deploy_from_scratch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clones the repo, sets up Postgres and the conda env, installs Airflow, starts it, and creates the &lt;code&gt;config_hq_bucket&lt;/code&gt; variable and &lt;code&gt;google_cloud_default&lt;/code&gt; connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;light_deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;git pull&lt;/code&gt; plus a rebuild of the app package. This is our normal deploy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;restart_airflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops and starts the scheduler, DAG processor and API server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;complete_teardown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops everything and removes the database, env and project files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Airflow's &lt;code&gt;AIRFLOW_HOME&lt;/code&gt; points at the cloned repo, so a DAG change is a&lt;br&gt;
commit followed by &lt;code&gt;light_deploy&lt;/code&gt;. &lt;code&gt;config_hq&lt;/code&gt; has no deploy script at all.&lt;br&gt;
&lt;code&gt;terraform apply&lt;/code&gt; builds the container with Cloud Build, pushes it, and&lt;br&gt;
rolls out a new Cloud Run revision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 7: Running it day to day
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Access&lt;/strong&gt; lives in Terraform variables, in three tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Grants&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iap_tunnel_members&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;open the tunnel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oslogin_members&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log in as a normal user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oslogin_admin_members&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log in with sudo (empty by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We put Google Groups in there, not individual people, so adding someone to&lt;br&gt;
the team is a group change and not a Terraform change. If you ever grant&lt;br&gt;
access by hand in an emergency, add it to &lt;code&gt;terraform.tfvars&lt;/code&gt; afterwards, or&lt;br&gt;
it drifts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the VM dies&lt;/strong&gt;, almost everything that matters lives somewhere else. The&lt;br&gt;
DAGs and the ML code are in git. Configs are in the versioned &lt;code&gt;config_hq&lt;/code&gt;&lt;br&gt;
bucket. Model wheels and Vertex AI outputs are in their own buckets. Getting&lt;br&gt;
back is &lt;code&gt;terraform apply&lt;/code&gt; on &lt;code&gt;airflow_vm&lt;/code&gt; and then &lt;code&gt;deploy_from_scratch&lt;/code&gt;,&lt;br&gt;
which is safe to re-run if it fails partway. What does not come back is&lt;br&gt;
Airflow's own Postgres database, with its run history. It lives on the VM's&lt;br&gt;
disk, and nothing in the repo backs it up yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tearing down&lt;/strong&gt; goes in reverse: &lt;code&gt;terraform destroy&lt;/code&gt; on &lt;code&gt;airflow_vm&lt;/code&gt; first,&lt;br&gt;
because it depends on &lt;code&gt;config_hq&lt;/code&gt;'s bucket, then &lt;code&gt;config_hq&lt;/code&gt;. The IAP brand,&lt;br&gt;
the OAuth client and the Terraform state objects stay behind and have to be&lt;br&gt;
removed by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;It is running now and costs less than half of what Composer would.&lt;/p&gt;

&lt;p&gt;There are still chores. Airflow upgrades and OS patching are on us. The&lt;br&gt;
Postgres backups mentioned above are still missing. And &lt;code&gt;config_hq&lt;/code&gt; uses a&lt;br&gt;
self-signed certificate until it gets a real domain, so the browser shows a&lt;br&gt;
warning every time someone opens it.&lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this one. Feel free to share your comments,&lt;br&gt;
especially if you made the opposite call and stayed on Composer.&lt;/p&gt;

&lt;p&gt;Regards,&lt;/p&gt;

&lt;p&gt;Erfan&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>dataengineering</category>
      <category>machinelearning</category>
      <category>mlops</category>
    </item>
    <item>
      <title>Designing Coding Agent Skills That Actually Work</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Sat, 30 May 2026 17:37:57 +0000</pubDate>
      <link>https://dev.to/erfankashani/designing-coding-agent-skills-that-actually-work-5bjl</link>
      <guid>https://dev.to/erfankashani/designing-coding-agent-skills-that-actually-work-5bjl</guid>
      <description>&lt;p&gt;I've spent the last few months obsessed with AI coding agents. Not just using them. Building alongside them and optimizing them for my use-cases. I've designed and tested various skills for agents like Claude Code, VScode Copilot, Codex, and Cursor, and I've watched closely to see where they hold up and where they quietly fall apart.&lt;/p&gt;

&lt;p&gt;And they do fall apart. Usually in the same way. The agent does great for three steps, then does something dumb on step four, it starts backtracking and analyzing went wrong, and by the time it figures out the issue, the conetxt window is almost full, leaving you with 3 &lt;code&gt;what-have-I-done-so-far.md&lt;/code&gt; files instead of a task done.&lt;/p&gt;

&lt;p&gt;After enough of those moments, I stopped trying to make the agent smarter. I changed how I think about skill design entirely. This blog is about that shift, and the patterns that came out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skill that taught me this
&lt;/h2&gt;

&lt;p&gt;The example I keep coming back to is a skill I built called &lt;code&gt;confluence-publisher&lt;/code&gt;. The origin is boring but honest: I didn't have corporate access to a Confluence MCP server, so my agent had no native way to publish pages. Instead of waiting on IT, I built the bridge myself: a skill that takes markdown and publishes it to Confluence.&lt;/p&gt;

&lt;p&gt;I used this skill design as my sandbox for my new skill framework. Every pain point I'd hit with other skills showed up here too, so I used it to work out what actually makes an agent skill reliable. The full code is on GitHub if you want to follow along:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/erfankashani/vscode-copilot-skill" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The mental shift
&lt;/h2&gt;

&lt;p&gt;Now to the part that changed how I work.&lt;/p&gt;

&lt;p&gt;When a skill misbehaves, the instinct is to constrain the &lt;em&gt;agent&lt;/em&gt;: add more rules, more guardrails, more "do not do this" in the prompt. That's the wrong knob. You end up fighting the model.&lt;/p&gt;

&lt;p&gt;The better move is to constrain the &lt;strong&gt;tools&lt;/strong&gt;, not the agent. Make the things the agent reaches for, the scripts, deterministic and predictable. Then let the agent do the one thing it's genuinely great at: understanding what the user wants and reasoning about it. The agent reads intent and decides. The scripts perform the action.&lt;/p&gt;

&lt;p&gt;This buys you two things. You get far fewer nondeterministic results, because the risky work runs as plain, tested code which you can itterate through and write tests for. Moreover, you shrink the agent's context window, because it's holding decisions, not implementation details. Most of what follows is just this idea applied in different places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scripts as decision boundaries
&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.amazonaws.com%2Fuploads%2Farticles%2Fk6cfefqrxpa3q5yyo464.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.amazonaws.com%2Fuploads%2Farticles%2Fk6cfefqrxpa3q5yyo464.png" alt="decision_boundaries" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most important choice in the whole design: &lt;strong&gt;the agent doesn't write code at runtime. It runs pre-written scripts and decides with the correct context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each step in the workflow is a standalone Python script — &lt;code&gt;step_01_convert.py&lt;/code&gt;, &lt;code&gt;step_02_publish.py&lt;/code&gt; — with a clean interface, just like any tool in the agent's toolkit. CLI arguments go in, structured output and exit codes come out. The agent's job shrinks to four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decide what arguments to pass&lt;/li&gt;
&lt;li&gt;Run the script&lt;/li&gt;
&lt;li&gt;Read the output&lt;/li&gt;
&lt;li&gt;Decide what to do next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That removes the ambiguity. Instead of asking the model to reason about Confluence API semantics, authentication, HTTP headers, and XHTML formatting on the fly, the scripts hold all that complexity. The agent only has to understand the &lt;em&gt;contract&lt;/em&gt;: what goes in, what comes out.&lt;/p&gt;

&lt;p&gt;In my tests, even a non-reasoning model like GPT-4o follows this without getting lost, because the SKILL.md reads like a recipe. Set your variables, run step 1, check the output, run step 2. There's no open-ended problem-solving at runtime, so there's very little room to improvise a mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failures that speak up
&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.amazonaws.com%2Fuploads%2Farticles%2Fy78z571w9yk1zeh4junm.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.amazonaws.com%2Fuploads%2Farticles%2Fy78z571w9yk1zeh4junm.png" alt="vocal_failures" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every script is built to &lt;strong&gt;fail loudly and specifically&lt;/strong&gt;. Exit codes aren't just 0 or 1 — they carry meaning:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;What the Agent Should Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;td&gt;Continue to next step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Conversion error&lt;/td&gt;
&lt;td&gt;Check the markdown input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API/auth error&lt;/td&gt;
&lt;td&gt;Verify credentials, run diagnostics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And when something breaks, the message isn't a useless &lt;code&gt;"failed"&lt;/code&gt;. It tells the agent exactly what's missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: --parent-page-id is required for create/upsert mode.
ERROR: Missing environment variables: CONFLUENCE_URL, CONFLUENCE_API_TOKEN
ERROR: HTML artifact not found: /path/to/expected/file.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what lets the agent self-correct. If it forgot &lt;code&gt;--parent-page-id&lt;/code&gt;, the error says so, and the agent fixes it. If credentials are missing, it knows to ask the user instead of guessing.&lt;/p&gt;

&lt;p&gt;I took this one step further with a &lt;code&gt;diagnose_permissions.py&lt;/code&gt; script. When step 2 fails on an auth error, the agent can run a dedicated diagnostic that tests connectivity, space access, and page-level permissions in sequence. Each test prints SUCCESS or FAILED on its own, so the agent can pinpoint exactly where the permission chain breaks, instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artifacts as memory
&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.amazonaws.com%2Fuploads%2Farticles%2Fhiqvctkv1f416gtusbm2.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.amazonaws.com%2Fuploads%2Farticles%2Fhiqvctkv1f416gtusbm2.png" alt="artifacts" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the hardest problems in agentic workflows is the context window. A 16,000-character HTML document doesn't need to live in the agent's memory. It just needs to be &lt;em&gt;reachable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;artifacts/&lt;/code&gt; folder handles that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1 converts markdown to XHTML and writes it to &lt;code&gt;artifacts/{Title}_{RUN_TS}.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Step 2 reads from that file path&lt;/li&gt;
&lt;li&gt;The agent only holds the &lt;em&gt;path&lt;/em&gt;, never the content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the agent can move large documents through the workflow without burning context. It can also inspect an artifact between steps if needed (ex. read the HTML to check the conversion looks right before publishing). The naming convention (&lt;code&gt;{Title}_{RunTimestamp}.html&lt;/code&gt;) makes each artifact self-describing, so if a run does something weird weeks later, you can find the exact file from that run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work-logs as governance
&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.amazonaws.com%2Fuploads%2Farticles%2Fqiw5qixi69iv2vlqmlmq.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.amazonaws.com%2Fuploads%2Farticles%2Fqiw5qixi69iv2vlqmlmq.png" alt="work_logs" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every script invocation appends to a shared work-log: &lt;code&gt;work-logs/worklogs_{RUN_TS}.md&lt;/code&gt;. This is more than logging — it's a record of what the skill actually did.&lt;/p&gt;

&lt;p&gt;A typical entry looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Step 02: publish&lt;/span&gt;
&lt;span class="gs"&gt;**Script**&lt;/span&gt;: &lt;span class="sb"&gt;`script/step_02_publish.py`&lt;/span&gt;
&lt;span class="gs"&gt;**Started**&lt;/span&gt;: 2026-05-28T18:36:24.677335+00:00

&lt;span class="gu"&gt;### Script Output&lt;/span&gt;
HTML artifact loaded: .github/.../Main_DAG_Analysis_20260528_143418.html (16631 chars)
Title: Main DAG Analysis
Space: LCA
Mode: create
Confluence connection established.
Creating page under parent 4180803775...
Page published successfully.
PAGE_ID=4238049513
PAGE_URL=https://company.atlassian.net/wiki/pages/viewpage.action?pageId=4238049513
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error tracking&lt;/strong&gt;: when a run fails mid-way, the log shows which step succeeded and which didn't, with timestamps and the exact parameters used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side-effect tracking&lt;/strong&gt;: publishing a page is irreversible. The log records &lt;em&gt;when&lt;/em&gt; it happened, &lt;em&gt;what&lt;/em&gt; went out, and &lt;em&gt;where&lt;/em&gt; it landed. If the skill fails on a later step, you know exactly what state it left behind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent self-reference or self-improvement&lt;/strong&gt;: on the next run, the agent can read past work-logs to see what happened before, what parameters worked, what failed, what the output looked like. It's a form of long-term memory that doesn't pollute the context with raw data and allows the agent to correct itself in case of past failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Secrets stay outside the context window
&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.amazonaws.com%2Fuploads%2Farticles%2Fovyrs7gnhhfvyabgszj9.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.amazonaws.com%2Fuploads%2Farticles%2Fovyrs7gnhhfvyabgszj9.png" alt="secrets_stay_outside" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credentials should not be exposed to the agent context window. The authentication should be done programatically and preferably from a service account designed for the agnet itself. The credentials can be read from secret managers and perform authentication aside from the agent's context. In this example, I am using a .env file that is not tracked on git for simplicity of demo. The scripts load the credentialls internally through &lt;code&gt;_load_env_file()&lt;/code&gt;. The agent never sees the token value in its context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_load_env_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Load variables from the skill&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s .env file into os.environ.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;env_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scripts are designed to check for required variables to be set and if they cannot identify them then a "Missing environment variables: CONFLUENCE_API_TOKEN" error would notify the agent. &lt;/p&gt;

&lt;p&gt;A &lt;code&gt;.template-env&lt;/code&gt; file shows the expected shape without real values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;CONFLUENCE_URL&lt;/span&gt;=&lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;company&lt;/span&gt;.&lt;span class="n"&gt;atlassian&lt;/span&gt;.&lt;span class="n"&gt;net&lt;/span&gt;
&lt;span class="n"&gt;CONFLUENCE_EMAIL&lt;/span&gt;=&lt;span class="n"&gt;your&lt;/span&gt;-&lt;span class="n"&gt;company&lt;/span&gt;-&lt;span class="n"&gt;email&lt;/span&gt;
&lt;span class="n"&gt;CONFLUENCE_API_TOKEN&lt;/span&gt;=&lt;span class="n"&gt;developer&lt;/span&gt;-&lt;span class="n"&gt;api&lt;/span&gt;-&lt;span class="n"&gt;token&lt;/span&gt;-&lt;span class="n"&gt;get&lt;/span&gt;-&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;confluence&lt;/span&gt;-&lt;span class="n"&gt;site&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The human operator manages the secrets, and the agent runs with whatever permissions the authenticated app already has. No OAuth flows, no token rotation for the agent to reason about. It just operates in the sandbox that is allowed to access. &lt;/p&gt;

&lt;h2&gt;
  
  
  Design for the floor, not the ceiling
&lt;/h2&gt;

&lt;p&gt;As you notice, the thread running through all of this is that SKILL.md is written in a way to reduce confusion so that a model &lt;em&gt;without&lt;/em&gt; chain-of-thought reasoning can also run it correctly. A few techniques make that work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Numbered steps with exact commands.&lt;/strong&gt; The agent doesn't figure out &lt;em&gt;how&lt;/em&gt; to call a script — the exact command is right there with placeholder variables marked clearly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One decision per step.&lt;/strong&gt; Each step has one outcome to check (the exit code) and one branch to take (continue or stop).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No nested logic.&lt;/strong&gt; There's no "if the page exists and the mode is upsert and the parent changed, then..." Each mode is independent, and the script handles its own logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured output.&lt;/strong&gt; Scripts print JSON on success, so any model can parse the result without extra parsing logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a deliberate tradeoff. A stronger model &lt;em&gt;could&lt;/em&gt; handle a messier interface, but by designing for the floor, the skill works across model tiers (even local models that run on the consumer devices).&lt;/p&gt;

&lt;h2&gt;
  
  
  When to reach for this (and when not to)
&lt;/h2&gt;

&lt;p&gt;A fair question for this audience: if MCP exists, why build a skill out of scripts at all? I started here because I didn't have MCP access, but the two aren't really competitors. An MCP server hands the agent a set of tools. This pattern hands the agent an ordered recipe, plus on-disk memory and an audit trail of what it did. You can even run both, and point the recipe at MCP tools instead of local scripts.&lt;/p&gt;

&lt;p&gt;The honest tradeoff: this works best for workflows that are mostly linear and have real side effects you want recorded. Publish, deploy, migrate, generate-and-ship. It's a poor fit when the work is exploratory or needs the agent to branch in ways you can't predict ahead of time. Pre-written scripts can't adapt to a situation you didn't script for, and writing them is upfront cost. If a task is a one-off, skip the ceremony and let the agent improvise. The moment it becomes something you'll run again and you care about the outcome, the structure pays for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If I had to compress everything I learned into one line: &lt;strong&gt;don't make the agent smarter. Make its tools more predictable.&lt;/strong&gt; Let the agent reason about intent, and let deterministic scripts do the acting.&lt;/p&gt;

&lt;p&gt;The same idea, broken down by what each piece does:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;How it's applied&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Remove ambiguity&lt;/td&gt;
&lt;td&gt;Pre-written scripts with CLI contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fail vocally&lt;/td&gt;
&lt;td&gt;Specific exit codes + descriptive error messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manage context&lt;/td&gt;
&lt;td&gt;Artifacts on disk, not in memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Govern side effects&lt;/td&gt;
&lt;td&gt;Work-logs capture every action with timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protect secrets&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.env&lt;/code&gt; loaded by scripts, never surfaced to the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design for simplicity&lt;/td&gt;
&lt;td&gt;Linear steps, exact commands, structured output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable self-correction&lt;/td&gt;
&lt;td&gt;Past logs readable by future runs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result is a skill that a high-reasoning model can design, but a basic model can still operate. It runs predictably, and you can trace exactly what it did.&lt;/p&gt;

&lt;p&gt;The full &lt;code&gt;confluence-publisher&lt;/code&gt; code is here if you want to dig in or fork it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/erfankashani/vscode-copilot-skill" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you enjoyed this, feel free to follow me on &lt;a href="https://www.linkedin.com/in/erfankashani/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; — I share what I learn as I keep building.&lt;/p&gt;

&lt;p&gt;Regards,&lt;/p&gt;

&lt;p&gt;Erfan&lt;/p&gt;

</description>
      <category>vscodecopilot</category>
      <category>runnerhchallenge</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Deploy Application on Azure App Services</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Wed, 28 Dec 2022 21:16:53 +0000</pubDate>
      <link>https://dev.to/erfankashani/deploy-application-on-azure-app-services-k32</link>
      <guid>https://dev.to/erfankashani/deploy-application-on-azure-app-services-k32</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello, after a long break of blogging, I am dedicating myself to share my knowledge with the community via series of posts regarding different architecture designs that I have worked on throughout past year. These designs corresponds to my current learning journey and are open to feedbacks and discussions. Without further due, let's dig in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Senario
&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.amazonaws.com%2Fuploads%2Farticles%2Fjfxvrwlerw9slrvh37hd.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.amazonaws.com%2Fuploads%2Farticles%2Fjfxvrwlerw9slrvh37hd.png" alt="demo-senario" width="800" height="539"&gt;&lt;/a&gt;&lt;br&gt;
Imagine you are in process of creating a product in form of web application, mobile application, or API endpoint. You have designed and prototyped your business logic in form of an executable package. While this application undergoes different levels of development maturity, you require a mean of presenting and demoing the proof of concept (POC) to your  client/audiences with different technical skill levels. There are plenty approaches which can achieve this purpose. Some are mentioned below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ship the application as a package to be installed and tested on other developer's laptops.&lt;/li&gt;
&lt;li&gt;Package and Deploy the application on an internal server, on-premises infrastructure or internal network for few techsavy audiences to test.&lt;/li&gt;
&lt;li&gt;Deploy your application and serve on a public/private cloud provider and create instructions for how the end-user would interact with your app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, you have many choices to deploy and present your application. One of the most important lessons I have learnt is that there will never be a silver-bullet tool, design, or architecture which works for all problems. Each project comes with sets of requirements and constraints. An architect or a developer has to make considerations and trade-off which will affect the product's end-goal. Some of the factors you can consider while choosing the correct approach for your product demo are time, skillset, budget, product's maturity level, and enterprise infrastructure availability. &lt;/p&gt;

&lt;p&gt;having said that, let us assume we decided to deploy our application to a cloud provider (such as Microsoft Azure). There are yet different abstraction levels we can choose to lower the entry-cost of using cloud services. The price of these abstractions are to lose control over certain parts of the infrastructure your application will run on. Some of the available options are as such:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy your application as a serverless architecture using Azure functions&lt;/li&gt;
&lt;li&gt;Containerize and deploy the application using one of the container delivery services on Azure like &lt;a href="https://azure.microsoft.com/en-us/products/app-service/#overview" rel="noopener noreferrer"&gt;App Services&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/products/container-instances/" rel="noopener noreferrer"&gt;Container Instances&lt;/a&gt;, or &lt;a href="https://azure.microsoft.com/en-us/products/kubernetes-service/" rel="noopener noreferrer"&gt;Kubernetes Services&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;Deploy the application on a cloud hosted Virtual Machine &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned before, there are situations which choosing one solution would be more beneficial than others. In this example, we would like to deploy our application with minimal architecture alteration and scalability in mind. We will try to containerize and deploy the application on Azure App Service. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe40qw6k3lah1x6ameld5.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.amazonaws.com%2Fuploads%2Farticles%2Fe40qw6k3lah1x6ameld5.png" alt="azure-app-service" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Azure App Service
&lt;/h2&gt;

&lt;p&gt;App Service is an Azure cloud offering which allows fast build, deploy, and scaling of Web, Mobile, and API applications. It supports .NET, .NET Core, Node.js, Java, Python, or PHP programs inside of containers. App Service provide a fully managed infrastructure which deals with maintenance, security patches, and scaling. With it's 99.95 percent service-level agreement (SLA)-backed uptime availability, App Service can be good candidate for some production grade applications. We will deploy our first App Service in the following section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp64ejtnue87zqy53t4d.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.amazonaws.com%2Fuploads%2Farticles%2Fyp64ejtnue87zqy53t4d.png" alt="architecture-diagram" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;az-cli&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Steps:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Generate a new resource group: We will use this logical container to easier manage, monitor, deploy, scale, grant access of resources to a specific group or identity.&lt;/li&gt;
&lt;li&gt;Create a new Azure Container Registry (ACR): This image registry hosts your docker images on Azure. For enterprise use-cases look into options such as Jfrog Artifactory.&lt;/li&gt;
&lt;li&gt;Create an Azure App Service Plan&lt;/li&gt;
&lt;li&gt;Create an Azure App Service&lt;/li&gt;
&lt;li&gt;Upload your docker image to ACR and configure the App Service to use the image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We will configure the components using Azure CLI for more interactive approach. For more advance use-cases we can look into Azure ARM templates or terraform for future blogs. &lt;/p&gt;
&lt;h3&gt;
  
  
  Login using az cli
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install azure-cli on mac-os&lt;/span&gt;
brew update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew &lt;span class="nb"&gt;install &lt;/span&gt;azure-cli

&lt;span class="c"&gt;# login&lt;/span&gt;
az login

&lt;span class="c"&gt;# check the active subscription:&lt;/span&gt;
az account show &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# change the active subscription (if needed):&lt;/span&gt;
az account &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--subscription&lt;/span&gt; &amp;lt;subscription_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Create new resource group
&lt;/h3&gt;

&lt;p&gt;Note: Naming conventions should follow consistent rules throughout your developments. It is a good idea to research your organizational naming conventions and follow them. I will use the following convention in a Camel Case format: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[Compname, Teamname, Projectname, Envname, Resourcelocation (abbreviated), Resourcetype (abbreviated)]&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;example: &lt;code&gt;SpacexFlycontrolLandingtimeProdCcRg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create a new resource group:&lt;/span&gt;
az group create &lt;span class="nt"&gt;--name&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--location&lt;/span&gt; canadacentral

&lt;span class="c"&gt;# check the resource group list&lt;/span&gt;
az group list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Create Azure Container Registry (ACR)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create ACR&lt;/span&gt;
az acr create &lt;span class="nt"&gt;--name&lt;/span&gt; demoflaskappacr &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--sku&lt;/span&gt; Basic &lt;span class="nt"&gt;--admin-enabled&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# retrieve the registry's credentials&lt;/span&gt;
az acr credential show &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--name&lt;/span&gt; demoflaskappacr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note: Copy the "password" (not password2) and username in the step above as we will use this to login via docker.&lt;/p&gt;
&lt;h3&gt;
  
  
  Login and push the image into ACR
&lt;/h3&gt;

&lt;p&gt;In this step you can use the boiler plate flask + docker project from &lt;a href="https://github.com/erfankashani/docker_flask_app" rel="noopener noreferrer"&gt;github&lt;/a&gt; and follow the instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# login to ACR &lt;/span&gt;
docker login demoflaskappacr.azurecr.io &lt;span class="nt"&gt;--username&lt;/span&gt; demoflaskappacr

&lt;span class="c"&gt;# clone the sample flask docker project&lt;/span&gt;
git clone https://github.com/erfankashani/docker_flask_app.git
&lt;span class="nb"&gt;cd &lt;/span&gt;docker_flask_app

&lt;span class="c"&gt;# build the image&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; docker-flask-app &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# run the image&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8888:5000 docker-flask-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This local container exposes to port 8888 on the host. The successful application can be checked on &lt;code&gt;http://localhost:8888/&lt;/code&gt; URL on your local computer's browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgigkfusnoxv5ucvoybt7.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.amazonaws.com%2Fuploads%2Farticles%2Fgigkfusnoxv5ucvoybt7.png" alt="app_service_deployed" width="799" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let us push this built image to docker registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# change the image name and tag to latest&lt;/span&gt;
docker tag docker-flask-app demoflaskappacr.azurecr.io/docker-flask-app:latest

&lt;span class="c"&gt;# push to registry&lt;/span&gt;
docker push demoflaskappacr.azurecr.io/docker-flask-app:latest

&lt;span class="c"&gt;# check the ACR for the pushed image&lt;/span&gt;
az acr repository list &lt;span class="nt"&gt;-n&lt;/span&gt; demoflaskappacr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create App service
&lt;/h3&gt;

&lt;p&gt;In this step we will create the App Service Plan and then an App Service which can pull images from the registry&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create app service plan:&lt;/span&gt;
az appservice plan create &lt;span class="nt"&gt;--name&lt;/span&gt; DemoFlaskappDevAsp &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--is-linux&lt;/span&gt;

&lt;span class="c"&gt;# create the app service (webapp)&lt;/span&gt;
az webapp create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--plan&lt;/span&gt; DemoFlaskappDevAsp &lt;span class="nt"&gt;--name&lt;/span&gt; DemoFlaskappDevAs &lt;span class="nt"&gt;--deployment-container-image-name&lt;/span&gt; demoflaskappacr.azurecr.io/docker-flask-app:latest

&lt;span class="c"&gt;# expose the container's port 5000&lt;/span&gt;
az webapp config appsettings &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--name&lt;/span&gt; DemoFlaskappDevAs &lt;span class="nt"&gt;--settings&lt;/span&gt; &lt;span class="nv"&gt;WEBSITES_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5000

&lt;span class="c"&gt;# enable the system assigned managed identity (principle-id):&lt;/span&gt;
az webapp identity assign &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--name&lt;/span&gt; DemoFlaskappDevAs &lt;span class="nt"&gt;--query&lt;/span&gt; principalId &lt;span class="nt"&gt;--output&lt;/span&gt; tsv

&lt;span class="c"&gt;# retrieve az account subscription-id (subscription-id)&lt;/span&gt;
az account show &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="c"&gt;# grant the pulling access from ACR to the webapp managed identity (use principle-id and subscription-id from above)&lt;/span&gt;
az role assignment create &lt;span class="nt"&gt;--assignee&lt;/span&gt; &amp;lt;principle-id&amp;gt; &lt;span class="nt"&gt;--scope&lt;/span&gt; /subscriptions/&amp;lt;subscription-id&amp;gt;/resourceGroups/DemoFlaskappDevCcRg/providers/Microsoft.ContainerRegistry/registries/demoflaskappacr &lt;span class="nt"&gt;--role&lt;/span&gt; &lt;span class="s2"&gt;"AcrPull"&lt;/span&gt;

&lt;span class="c"&gt;# configure the app to use the managed identity (use subscription-id from above)&lt;/span&gt;
az resource update &lt;span class="nt"&gt;--ids&lt;/span&gt; /subscriptions/&amp;lt;subscription-id&amp;gt;/resourceGroups/DemoFlaskappDevCcRg/providers/Microsoft.Web/sites/DemoFlaskappDevAs/config/web &lt;span class="nt"&gt;--set&lt;/span&gt; properties.acrUseManagedIdentityCreds&lt;span class="o"&gt;=&lt;/span&gt;True

&lt;span class="c"&gt;# configure the web app to use the correct docker registry:&lt;/span&gt;
az webapp config container &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; DemoFlaskappDevAs &lt;span class="nt"&gt;--resource-group&lt;/span&gt; DemoFlaskappDevCcRg &lt;span class="nt"&gt;--docker-custom-image-name&lt;/span&gt; demoflaskappacr.azurecr.io/docker-flask-app:latest &lt;span class="nt"&gt;--docker-registry-server-url&lt;/span&gt; https://demoflaskappacr.azurecr.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grab the URL from azure portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5er0pfuljadhwank7jn.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.amazonaws.com%2Fuploads%2Farticles%2Fc5er0pfuljadhwank7jn.png" alt="app_service_portal" width="799" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;use the URL to access the app (please note that the first time load might take longer):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgigkfusnoxv5ucvoybt7.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.amazonaws.com%2Fuploads%2Farticles%2Fgigkfusnoxv5ucvoybt7.png" alt="app_service_deployed" width="799" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have a deployed working version of this App Service. There are few more considerations which we make before demoing this application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Networking
&lt;/h2&gt;

&lt;p&gt;The applications hosted on Azure App Service are by default accessible through public internet and can only reachout to public endpoints. There are series of networking features which can help you define guidelines on the app's ingress (inbound traffic) and egress (outbound traffic). It is important to note that some of these capabilities are dependant on your mode of deployment (aka. single-tenant vs. multi-tenant plans). The following features are available on our multi-tenant service hosted App Service:&lt;/p&gt;

&lt;h4&gt;
  
  
  Ingress:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App-assigned address&lt;/strong&gt;: Enables IP-based SSL checks and dedicated unshared inbound addresses for your app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access restriction&lt;/strong&gt;: Can limit the app access to specific well-defined addresses. This is a good idea for early demo phases when you would want to ensure your application can be viewed only by specific people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service endpoints and private endpoints&lt;/strong&gt;: Expose a private endpoint and manage the access from resources within a VNet. There are more advanced techniques such as integrating your application with firewalls and load-balancer services like Azure Front Door. These applications are more useful for production and we can discuss them in future blogs based on interest levels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Egress:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid connections&lt;/strong&gt;: Access to private networks that are not connected to Azure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway-required Vnet-integrations&lt;/strong&gt;: Access to resources  in other VNets in different region via VNet peering. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VNet Integrations&lt;/strong&gt;: This integration with Virtual Network opens the door to options such as configuring private subnets, exposing static IPs via NAT gateway, defining Network Security Groups (NSGs) for outbound traffic, integrating your app with a secure hub-and-spoke model to communicate with on-premises resources etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User Authentication
&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.amazonaws.com%2Fuploads%2Farticles%2Fw94vmojocjddzl79kt5g.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.amazonaws.com%2Fuploads%2Farticles%2Fw94vmojocjddzl79kt5g.png" alt=" " width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this section we will enable user authentication using Microsoft AD Identity provider. Navigate to your App Service resource inside azure portal. Then click on authentication and "Add Identity Provider":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1ba0pm8hluwpk9reqbe.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.amazonaws.com%2Fuploads%2Farticles%2Fr1ba0pm8hluwpk9reqbe.png" alt=" " width="799" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select "Microsoft" as identity provider:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrzko31e8i1mfnhjw1sx.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.amazonaws.com%2Fuploads%2Farticles%2Ftrzko31e8i1mfnhjw1sx.png" alt="choose-microsoft-identity-provider" width="759" height="782"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your authenticator will require an app registration or service principal name (SPN) to communicate with the Identity provider. You have the preferred options of creating a new app registration from the same page or use an existing app registration followed by &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad#-create-an-app-registration-in-azure-ad-for-your-app-service-app" rel="noopener noreferrer"&gt;this link&lt;/a&gt;. In this example we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow application access to same tenant users only.&lt;/li&gt;
&lt;li&gt;restrict app access to authenticated requests.&lt;/li&gt;
&lt;li&gt;return 302 Error code on unauthenticated requests.&lt;/li&gt;
&lt;li&gt;enable token storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then press "add" which brings the following result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5jqra4jbsuwazyxumal.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.amazonaws.com%2Fuploads%2Farticles%2Fi5jqra4jbsuwazyxumal.png" alt="app-service-authenticated" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To try this feature, attempt to access the URL again. This will require a Microsoft account login and the following pop up would show up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58acfsg7bmdq2mqzm2jb.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.amazonaws.com%2Fuploads%2Farticles%2F58acfsg7bmdq2mqzm2jb.png" alt="autentication-page" width="800" height="906"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Scaling
&lt;/h2&gt;

&lt;p&gt;there are two means of scaling your application to support higher number of requests:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling-up&lt;/strong&gt;: Means adding more resources like CPU, memory, and disk space to your existing app. You can achieve this by altering the App service Plan tier which your App Service is using.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnh2825qdlvwpe9vqglg.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.amazonaws.com%2Fuploads%2Farticles%2Fhnh2825qdlvwpe9vqglg.png" alt="scaling-up" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling-out&lt;/strong&gt;: Create more instances of the VMs that run your application. You can scale out to as many as 30 instances, depending on your pricing tier. There are also autoscaling options to scale up and down your instances based on rules and schedules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60jnzsw24595kx60ekgq.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.amazonaws.com%2Fuploads%2Farticles%2F60jnzsw24595kx60ekgq.png" alt="scaling-out" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for following this blog to the end. In the next blog we will look into other aspects of this solution such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secret management &lt;/li&gt;
&lt;li&gt;Deployment options&lt;/li&gt;
&lt;li&gt;Monitoring&lt;/li&gt;
&lt;li&gt;Disaster Recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/products/app-service/" rel="noopener noreferrer"&gt;https://azure.microsoft.com/en-us/products/app-service/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/app-service/networking-features" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/app-service/networking-features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/app-service/manage-scale-up" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/app-service/manage-scale-up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/app-service-web-app/basic-web-app?tabs=cli" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/app-service-web-app/basic-web-app?tabs=cli&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Awesome diagrams using Mermaid.js</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Thu, 14 Apr 2022 13:02:01 +0000</pubDate>
      <link>https://dev.to/erfankashani/awesome-diagrams-using-mermaidjs-3am0</link>
      <guid>https://dev.to/erfankashani/awesome-diagrams-using-mermaidjs-3am0</guid>
      <description>&lt;p&gt;Today I want to share an awesome library which I discovered recently. Creating diagrams for complicated software can be a time consuming and tricky if you are not using the right tool. Maintaining the diagram to show the most recent structure can be struggle. One of the most common go-to is &lt;a href="https://app.diagrams.net/" rel="noopener noreferrer"&gt;diagram.net&lt;/a&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fconocimientolibre.mx%2Fwp-content%2Fuploads%2F2020%2F05%2Fdiagrams-660x330.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%2Fconocimientolibre.mx%2Fwp-content%2Fuploads%2F2020%2F05%2Fdiagrams-660x330.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This web application allow users to drag and drop elements on a page and rename them to create variety of graphs. In my experience, this application can create really beautiful graphs which take a long Time to create. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.diagrams.net%2Fassets%2Fsvg%2Fhome-dia2.svg" 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%2Fwww.diagrams.net%2Fassets%2Fsvg%2Fhome-dia2.svg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My search was not done there. I aim to introduce speed in the diagram creations, version control, and easier interface than finding elements, resizing, and renaming them plenty of times. This is where I found &lt;a href="https://mermaid-js.github.io/mermaid/#/" rel="noopener noreferrer"&gt;Mermaid&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid-js.github.io%2Fmermaid%2Fimg%2Fheader.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%2Fmermaid-js.github.io%2Fmermaid%2Fimg%2Fheader.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This library is written in Javascript and allows diagram creation using a markdown like syntax. Mermaid can create the following diagrams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flowchart&lt;/li&gt;
&lt;li&gt;Sequence diagram &lt;/li&gt;
&lt;li&gt;Class diagram&lt;/li&gt;
&lt;li&gt;State Diagram&lt;/li&gt;
&lt;li&gt;Entity Relationship diagram&lt;/li&gt;
&lt;li&gt;User journey&lt;/li&gt;
&lt;li&gt;Gantt&lt;/li&gt;
&lt;li&gt;Pie Chart&lt;/li&gt;
&lt;li&gt;Requirement diagram&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of the benefits of using Mermaid.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating graphs as code&lt;/li&gt;
&lt;li&gt;saving time on styling the graphs&lt;/li&gt;
&lt;li&gt;version controlling your graphs&lt;/li&gt;
&lt;li&gt;easy integration with other libraries like diagram.net&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start we can install the “Markdown Preview Mermaid Support” extension on VSCode:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdq5w1pp9kfu1fyve457.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.amazonaws.com%2Fuploads%2Farticles%2Fgdq5w1pp9kfu1fyve457.png" alt=" " width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then create a diagram directory in your desired codebase (optional). Finally create a markdown file like “app_diagram.md”. Choose the small (open preview to the side) icon on too right corner of your editor or ( shift + command + v for mac). Now you are ready to edit the file. Lets start with some easy examples:&lt;/p&gt;

&lt;p&gt;The following showcases a Flowchart using mermaid:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;graph&amp;nbsp;TD;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;        A--&amp;gt;B;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;        A--&amp;gt;C;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;        B--&amp;gt;D;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;        C--&amp;gt;D;&lt;/code&gt;&lt;/pre&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.amazonaws.com%2Fuploads%2Farticles%2Fjoejiw5t20znp0bnulz5.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.amazonaws.com%2Fuploads%2Farticles%2Fjoejiw5t20znp0bnulz5.png" alt=" " width="233" height="261"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Any mermaid code should be entered as a code snippet with “mermaid” tag. Each letter represents a &lt;strong&gt;node&lt;/strong&gt; which are connected via &lt;strong&gt;edges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are many samples of diagram creation on &lt;a href="https://mermaid-js.github.io/mermaid/#/" rel="noopener noreferrer"&gt;the mermaid website&lt;/a&gt;. I am going to demonstrate a settup that worked for me while creating flowcharts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;lt;!-- ```mermaid
flowchart Guide
    direction TB
    f[flow]
    s[[Step]]
    i([input])
    d[(Database)]
    Parameters&amp;gt;Parameters]
``` --&amp;gt;
&amp;lt;!-- Resource: https://mermaid-js.github.io/mermaid/#/ --&amp;gt;
&amp;lt;!-- Look at the graph here: https://mermaid.live --&amp;gt;
&amp;lt;!-- Connect to diagrams.net: https://www.diagrams.net/blog/mermaid-diagrams --&amp;gt;

```mermaid
flowchart LR
    subgraph Guide
        direction TB

        f[flow]
        s[[Step]]
        d[(Database)]
        Parameters&amp;gt;Parameters]
    end

    %% add in-line style
    Guide:::someclass
    classDef someclass fill:#f96;


    %%list of parameters
    p1&amp;gt;bank_name]
    p2&amp;gt;seller_info]


    %%list of steps
    s1[[1-withdraw_money]]
    s2[[2-purchase_bike]]


    %%list of flows
    f1[go_to_bank]
    f2[withdraw_from_atm]
    f3[contact_seller]
    f4[trade_bike]


    %%list of databases
    d1[(reads: customer_identification)]
    d2[(reads/writes: customer_balance)]
    d3[(writes: e_document_sign)]


    %% Create the step flows
    s1-.-&amp;gt;s2;


    %% s1 flow
    p1--&amp;gt;|inputs|s1
    s1--&amp;gt;|calls|f1
    f1--&amp;gt;|calls|f2
    f2--&amp;gt;d1
    f2--&amp;gt;d2


    %% s2 flow
    p2--&amp;gt;|inputs|s2
    s2--&amp;gt;f3
    f3--&amp;gt;f4
    f4--&amp;gt;d3
\```

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fdharwi2lje6mc7444331.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.amazonaws.com%2Fuploads%2Farticles%2Fdharwi2lje6mc7444331.png" alt=" " width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also other resources where you can export your graphs as PNG or SVG like &lt;a href="https://mermaid.live" rel="noopener noreferrer"&gt;mermaid.live&lt;/a&gt;. Moreover, you can checkout &lt;a href="https://www.diagrams.net/blog/mermaid-diagrams" rel="noopener noreferrer"&gt;this blog&lt;/a&gt; regarding how to transfer your graphs from mermaid to diagram.net (FYI, digram.net does not support all mermaid functionalities) &lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this tutorial. Feel free to express your comments.&lt;/p&gt;

&lt;p&gt;Regards,&lt;/p&gt;

&lt;p&gt;Erfan&lt;/p&gt;

</description>
      <category>graph</category>
      <category>diagram</category>
      <category>libraries</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Nail the Fundamentals : Git_Good (Git flow best practices)</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Sat, 28 Mar 2020 19:06:46 +0000</pubDate>
      <link>https://dev.to/erfankashani/nail-the-fundamentals-gitgucci-team-git-pipeline-3p6p</link>
      <guid>https://dev.to/erfankashani/nail-the-fundamentals-gitgucci-team-git-pipeline-3p6p</guid>
      <description>&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%2Fthenypost.files.wordpress.com%2F2020%2F01%2Ftime-machine-inventor-01.jpg%3Fquality%3D80%26strip%3Dall" 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%2Fthenypost.files.wordpress.com%2F2020%2F01%2Ftime-machine-inventor-01.jpg%3Fquality%3D80%26strip%3Dall" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you ever you wished you had a time machine to go back in time and fix that awkward thing you said to someone? or you wanted to choose a different path of decisions for your life? ... as much as I want to write a tutorial on time machines, This blog is about a valuable tool similar to a time machine for developers ...  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.vox-cdn.com%2Fthumbor%2FZlgJZZHewoP4X9oOp9v9WiWi7yc%3D%2F1400x1050%2Ffilters%3Aformat%28jpeg%29%2Fcdn.vox-cdn.com%2Fuploads%2Fchorus_asset%2Ffile%2F16213725%2Fgit.jpg" 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%2Fcdn.vox-cdn.com%2Fthumbor%2FZlgJZZHewoP4X9oOp9v9WiWi7yc%3D%2F1400x1050%2Ffilters%3Aformat%28jpeg%29%2Fcdn.vox-cdn.com%2Fuploads%2Fchorus_asset%2Ffile%2F16213725%2Fgit.jpg" alt="git" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Git is your best friend when it comes to coding and I regret not using it enough during my undergraduate education. Today I want to discuss the git flow I use day to day in my workplace and hope it brings value to your development journey.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6752kbudqalskkmp6sk0.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.amazonaws.com%2Fuploads%2Farticles%2F6752kbudqalskkmp6sk0.png" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any repository has two main branches: &lt;strong&gt;Master&lt;/strong&gt; and &lt;strong&gt;Development&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Master (Production):&lt;/strong&gt; is the production code which is being used by the customers. This branch should be ideally bug free. This is not a playground to test new lines of code. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development (Staging):&lt;/strong&gt; is the next version of Staging (Master) environment. This means this branch is still close to the master but has one or more new features  which are being tested.&lt;/p&gt;





&lt;p&gt;To start a new feature in the code base we should:&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Start with the latest version of the development branch&lt;/strong&gt; : This is important to always keep your development branch up to date. In big teams you can usually followup with the newest merge requests via the slack or email. This indicates that someone will be altering the current state of development soon and you should pull the latest version of the code: (this avoids future merge conflicts).&lt;/p&gt;

&lt;p&gt;to perform this task you can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout development
git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;







&lt;p&gt;2) &lt;strong&gt;Create your new branch:&lt;/strong&gt; You should create a new branch to work on your new feature and avoid working on development branch. There is usually a convention for naming your branches among your team. My teams's convention is as such: &lt;code&gt;&amp;lt;initials&amp;gt;-&amp;lt;ticket-number&amp;gt;-&amp;lt;featurename&amp;gt;&lt;/code&gt; for example I would make my branch using following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; ek-132-slackbotapirouting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;







&lt;p&gt;3) &lt;strong&gt;Work on your branch:&lt;/strong&gt; You can make any changes you like to this branch and make sure you commit your changes often as you can revert to older version of code when needed. The commit messages should be a one liner description of what has changes in that commit. This will help you later if you wish to change or &lt;code&gt;squash&lt;/code&gt; your commits. Couple of useful git commands when adding and committing code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff                                &lt;span class="c"&gt;# will show you all the changes you made. Here if you think you don’t want to add a file just add it to the .gitignore file&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;                               &lt;span class="c"&gt;# will add all the changes &lt;/span&gt;
git add &lt;span class="nt"&gt;-p&lt;/span&gt;                              &lt;span class="c"&gt;# here you can see the changes one by one and write (y/n) to add or not add a change &lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; “what this change does”   &lt;span class="c"&gt;# to commit the added changes &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;







&lt;p&gt;4) &lt;strong&gt;Time to push the code up:&lt;/strong&gt; When you have the final version of your code, documented, tested, and refactored, it is the time to add this feature or the development branch. &lt;/p&gt;

&lt;p&gt;First we will rebase with the development branch to compare the latest changes on development with our branch and deal with any possible merge conflicts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout development
git pull
git checkout ek-132-slackbotapirouting
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; development.                   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You triggered the interactive rebasing. Here you can “reword” or “fixup” commits (basically squashing the commits ) and deal with code-conflict. After rebasing is done, it is time for pushing the code up to the development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;-f&lt;/span&gt;                       &lt;span class="c"&gt;#Force pushing to the ek-132-slackbotapirouting branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Currently you have your latest changes pushed to the remote branch and rebased with the current state of development branch. This means you will not face a merge conflict when you submit a merge request.&lt;/p&gt;





&lt;p&gt;5) &lt;strong&gt;Create the Merge request:&lt;/strong&gt; Now you can create a merge request by going on the web version of your remote repository (ex. &lt;a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests" rel="noopener noreferrer"&gt;Github&lt;/a&gt; or Gitlab). Assign the merge request to your software lead for review and copy the request link into the developer's chat for extra code-review. Enable the checkmark for deleting the branch after merging so your repository stays clean.&lt;/p&gt;





&lt;p&gt;6) &lt;strong&gt;Delete the feature branch:&lt;/strong&gt; It is always recommended to keep the repository clean.&lt;/p&gt;

&lt;p&gt;Deleting the feature branch locally by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;--delete&lt;/span&gt; ek-132-slackbotapirouting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 &lt;br&gt;
Deleting the branch remotely by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin &lt;span class="nt"&gt;--delete&lt;/span&gt; ek-132-slackbotapirouting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fyr2ccmc12ouj34krsvm8.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.amazonaws.com%2Fuploads%2Farticles%2Fyr2ccmc12ouj34krsvm8.png" width="624" height="329"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;Good Practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete your local and remote branch after merging to keep the repository clean&lt;/li&gt;
&lt;li&gt;If you have commits like "fixed a bug" or "fixed white space" make sure to squash them before making merge request&lt;/li&gt;
&lt;li&gt;Always rebase before submitting merge request to avoid merge-conflicts&lt;/li&gt;
&lt;li&gt;Avoid committing specific files to your development machine or process(ex. Gemfile.lock, .env).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>gitflow</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Nail the Fundamentals : Documenting Using Yardoc (YARD)</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Fri, 11 Oct 2019 23:09:18 +0000</pubDate>
      <link>https://dev.to/erfankashani/nail-the-fundamentals-documenting-using-yardoc-yard-51d5</link>
      <guid>https://dev.to/erfankashani/nail-the-fundamentals-documenting-using-yardoc-yard-51d5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Nail the Fundamental series attempt to discuss good coding practices. Theses are personal industry lessons, taught by senior developers and software architects to enhance my skills. Most of the topics are general coding principles while some of them focus on specific coding language. &lt;/p&gt;

&lt;p&gt;If you are a junior developer, this is the right place for you as I aim to fill the gap between coding lessons taught in the academia and what happens in the the industry. If you are a senior developer, you can compare your methods with the ones practiced by different organizations in &lt;em&gt;the comments&lt;/em&gt;.  &lt;/p&gt;




&lt;p&gt;Without further ado, In this tutorial we will look into YARD! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flylk6rph8g1xjfe0bb4b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flylk6rph8g1xjfe0bb4b.gif" width="352" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not that yard ... but YARD as in (Yay, A Ruby Documentation Tool). &lt;em&gt;YARD&lt;/em&gt; or &lt;em&gt;Yardoc&lt;/em&gt; is a documentation generating gem for the Ruby programming language. &lt;/p&gt;

&lt;p&gt;Before we jump into it, there is an important question to answer!&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;em&gt;Why should I document?&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bxngzrw6empypetqw0d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bxngzrw6empypetqw0d.gif" width="220" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a fair question and I carried it when I started working in a development team. Some of the most important reasons to document your codes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You will be using them in 6 months:&lt;/strong&gt; Code that you wrote 6 months ago is often indistinguishable from code that someone else has written. Documentation is the key to understand the code's syntax and implementation. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increasing usability of the code:&lt;/strong&gt; If you writing a feature, the code most likely will be integrated into a bigger project. Therefore, you should document it to be easily understood and interpreted by other developers. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhancing the On-boarding process:&lt;/strong&gt; In case of adding a new developer to your team or on-boarding a new department to use your internal tool, documentation reduces the time and effort for them to adapt the codebase.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplifying the debugging:&lt;/strong&gt; Software logs help to indicate where to look for a bug and documentations allow pinpointing the issue in low-level.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assisting with test-cases:&lt;/strong&gt; Black-box testing which analyzes functionality of the software relies solely on the requirement specifications document of your code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope by now you have enough reasons to start documenting your code. That definitely makes your code look more professional. so please ...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l1edrjnpu3ko12sufe4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l1edrjnpu3ko12sufe4.gif" width="498" height="276"&gt;&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Yardoc?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Yardoc&lt;/em&gt; is one of the well known ruby documentation tools and its benefits can be summarized as:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can be hosted on personal DSL and private servers.&lt;/li&gt;
&lt;li&gt;Generates consistant documentation.&lt;/li&gt;
&lt;li&gt;Customizable documentation template (using markup language).&lt;/li&gt;
&lt;li&gt;Accepts metadata such as &lt;em&gt;tags&lt;/em&gt; along with the documentation (similar to Python, Java, and Objective-C).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# First make sure Rdoc is installed (dependancy on Ubuntu)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;rdoc

&lt;span class="c"&gt;# Mac users don't need to install rdoc since it is installed with&lt;/span&gt;
&lt;span class="c"&gt;# the newest ruby installation.&lt;/span&gt;

&lt;span class="c"&gt;# Next, install YARD using gem:&lt;/span&gt;
gem &lt;span class="nb"&gt;install &lt;/span&gt;yard

&lt;span class="c"&gt;# Finally, to ensure YARD is installed&lt;/span&gt;
yard &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Documentation Styles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Most common tags:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt;&lt;/strong&gt; = Describes the &lt;em&gt;input parameters&lt;/em&gt; and their types (variable name can place before or after the type).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;code&gt;# @param [String] name Inputs the name of the user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;# @param [Numeric] age Inputs the age of the user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;# @param list [Array&amp;lt;String, Symbol&amp;gt;] the list of strings and symbols.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@option&lt;/strong&gt; = In case you dealing with different &lt;em&gt;options&lt;/em&gt; for the same input (use this for hash or array-list).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;code&gt;# @param [Hash] area_parameter numeric Inputs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;# @option area_parameter [Integer] :length&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;# @option area_parameter [Integer] :width&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@return&lt;/strong&gt; = Documents the &lt;em&gt;return-parameter&lt;/em&gt; from a method.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;code&gt;# @return [String] greeting message for the user.&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/since"&gt;@since&lt;/a&gt;&lt;/strong&gt; = Usually, &lt;code&gt;since&lt;/code&gt; is used to show &lt;em&gt;app versions&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;code&gt;# @since v2.0.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@raise&lt;/strong&gt; = Use it only if method raises any &lt;em&gt;exceptions&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;code&gt;# @raise [ZeroDivisionError] expectation if dividing by zero&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/see"&gt;@see&lt;/a&gt;&lt;/strong&gt; = Refer user to a &lt;em&gt;URL&lt;/em&gt; or &lt;em&gt;other class&lt;/em&gt; within the documentation for further explanation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;code&gt;# @see www.yardoc.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/note"&gt;@note&lt;/a&gt;&lt;/strong&gt; = Miscellaneous messages that are &lt;em&gt;low priority&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/todo"&gt;@todo&lt;/a&gt;&lt;/strong&gt; = Miscellaneous messages that are &lt;em&gt;high priority&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;@example&lt;/strong&gt; = Demonstrates an &lt;em&gt;example&lt;/em&gt; of how to call the method.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# @example Raise an exception using a message from +debug_message+&lt;/span&gt;
&lt;span class="c1"&gt;#   raise AppDebug.fetch('job_number', app_name: 'ExampleApp')&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common practice
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;add the file &lt;code&gt;.yardoc&lt;/code&gt; and &lt;code&gt;/doc&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; sine they can be generated easily when needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Document your private and protected methods. They will not be seen or used by the users but it helps other developers working on the same codebase. It is important to assure the private methods are not visible if you plan to publish the documentations. You can hide them in generating phase by:&lt;br&gt;
&lt;code&gt;yardoc --no-private --protected&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When documenting a method, first line holds the &lt;em&gt;Description&lt;/em&gt;. Leave a blank &lt;code&gt;#&lt;/code&gt; between the &lt;em&gt;description&lt;/em&gt; and &lt;em&gt;tags&lt;/em&gt;. For Example:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# this is the method's description. &lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# @param [String] text_input input parameter&lt;/span&gt;
&lt;span class="c1"&gt;# @return [String] returns the printed text&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#some code ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep the order of &lt;em&gt;input parameters&lt;/em&gt; consistent with the &lt;em&gt;param&lt;/em&gt; orders. This results in easier flow for the reader. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrap the text with &lt;code&gt;+&lt;/code&gt; to highlight&lt;br&gt;
&lt;code&gt;+highlighted_text+&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hands-on Demonstration
&lt;/h2&gt;

&lt;p&gt;The code is available on &lt;a href="https://github.com/erfankashani/Yardoc_Practice.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. clone the repository using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/erfankashani/Yardoc_Practice.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets look into the files inside:&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;Yardoc_Practice
nano practise.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Ffb18x85jpdabbi4bc70d.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.amazonaws.com%2Fuploads%2Farticles%2Ffb18x85jpdabbi4bc70d.png" alt="Alt Text" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the &lt;em&gt;tags&lt;/em&gt; and the best practices are demonstrated in the code. Feel free to use this file as a reference for &lt;code&gt;yardoc&lt;/code&gt;. Next step is to generate the documentation and spin-up the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Yardoc
&lt;/h3&gt;

&lt;p&gt;1- Go inside of the repository which you are using&lt;br&gt;
2- Create the documentation by: &lt;code&gt;yardoc &amp;lt;name_of_the_file&amp;gt;&lt;/code&gt;, in our case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;yardoc&lt;/span&gt; &lt;span class="n"&gt;practise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;          
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3- Run the web page on your local network (&lt;code&gt;Port:8808&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;yard&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;         
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this step, you will notice a new directory called 'doc'. This is where all the &lt;code&gt;HTML&lt;/code&gt; and &lt;code&gt;css&lt;/code&gt; files are kept to create the documentation webpage. Observe the documentations by typing &lt;code&gt;localhost:8808&lt;/code&gt; in your address-bar of a browser. Take a look at the available methods by choosing &lt;code&gt;methods&lt;/code&gt; option from the top left menu. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgsafe.org%2F5f%2F5feafca273.jpeg" 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%2Fi.imgsafe.org%2F5f%2F5feafca273.jpeg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By now, I hope you have a good understanding of why we document and how we do it using 'yardoc'. As you follow these simple but effective practices mentioned in the series, you will notice your code quality's improvement. Take the first step by start documenting all your ruby codebases. Let's get ittt...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2fe87jzc6pvr92op8kt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2fe87jzc6pvr92op8kt.gif" width="300" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/lsegal/yard" rel="noopener noreferrer"&gt;https://github.com/lsegal/yard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/chetan/1827484" rel="noopener noreferrer"&gt;https://gist.github.com/chetan/1827484&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.geeksforgeeks.org/differences-between-black-box-testing-vs-white-box-testing/" rel="noopener noreferrer"&gt;https://www.geeksforgeeks.org/differences-between-black-box-testing-vs-white-box-testing/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>documentation</category>
      <category>doc</category>
    </item>
    <item>
      <title>Long Story Short: Kubernetes</title>
      <dc:creator>erfankashani</dc:creator>
      <pubDate>Mon, 30 Sep 2019 00:05:40 +0000</pubDate>
      <link>https://dev.to/erfankashani/long-story-short-kubernetes-365</link>
      <guid>https://dev.to/erfankashani/long-story-short-kubernetes-365</guid>
      <description>&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%2Fi2.wp.com%2Fwww.integrationqa.com%2Fwp-content%2Fuploads%2F2019%2F02%2Fgitlab_gke_banner.png%3Fresize%3D646%252C366%26ssl%3D1" 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%2Fi2.wp.com%2Fwww.integrationqa.com%2Fwp-content%2Fuploads%2F2019%2F02%2Fgitlab_gke_banner.png%3Fresize%3D646%252C366%26ssl%3D1" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: Long Story Short series are personal learning experiences  as I encounter different development journeys. The aim of these series are to summarize and simplify difficult development concepts and teach them in different levels of proficiency.&lt;/p&gt;

&lt;h4&gt;
  
  
  The most important lesson I want you to take from this blog is to understand what kubernetes is? and why we use it?
&lt;/h4&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%2Fickosovo.com%2Fimages%2Fuploads%2Fphotos%2Fmicroservices_architecture.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%2Fickosovo.com%2Fimages%2Fuploads%2Fphotos%2Fmicroservices_architecture.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the era of &lt;strong&gt;micro-service architecture&lt;/strong&gt;, applications are structured as a collection of services which are &lt;strong&gt;loosely coupled&lt;/strong&gt;, &lt;strong&gt;easily maintained&lt;/strong&gt;, &lt;strong&gt;independently organized&lt;/strong&gt;, and owned by smaller teams. This is possible by &lt;strong&gt;containerizing&lt;/strong&gt; different services within an application and using API calls to communicate among them. &lt;/p&gt;

&lt;p&gt;This all sounds great until your organization thinks of &lt;strong&gt;scaling up&lt;/strong&gt; or achieving above 90% up-time for the application. Managing couple of containers is not an exhausting activity but when thinking of configuring, deploying, and maintaining 100s of containers simultaneously   our approach should be re-evaluated. This is where Kubernetes comes in the picture and automates the container management tasks.        &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fkubernetes_training-camptocamp.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%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fkubernetes_training-camptocamp.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Information:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Kunernetes:&lt;/strong&gt; Is an open-source software which helps managing and orchestrating containers.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It was first developed by google engineers and later donated to Cloud Native Computing Foundation (CNCF).&lt;/li&gt;
&lt;li&gt;It is an orchestration tool to deploy, scale up, and manage multiple containers together, therefore automating and enhancing the maintenance process of micro-services. Kubernetes main benefits are:

&lt;ul&gt;
&lt;li&gt;assist communicating among containers&lt;/li&gt;
&lt;li&gt;Deploy them properly &lt;/li&gt;
&lt;li&gt;Manage them carefully &lt;/li&gt;
&lt;li&gt;Auto scaling: Always making sure there is certain number of container's copies available. &lt;/li&gt;
&lt;li&gt;Load balancing: Distributing the traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes cluster are usually made up of two main parts:

&lt;ul&gt;
&lt;li&gt;Master (schedules application services)&lt;/li&gt;
&lt;li&gt;Nodes (listens to the orders from the master)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fblog.newrelic.com%2Fwp-content%2Fuploads%2Fkubernetes_architecture.jpg" 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/http%3A%2F%2Fblog.newrelic.com%2Fwp-content%2Fuploads%2Fkubernetes_architecture.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have a bigger picture of kubernetes and why we use it, let's dive inside of the cluster architecture and learn about the different components. &lt;/p&gt;

&lt;h2&gt;
  
  
  Components
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Master:&lt;/strong&gt; Is the control plane (main controller) of the cluster. Usually, users communicate with the master through &lt;strong&gt;dashboard graphical interface (GUI)&lt;/strong&gt; or the &lt;strong&gt;command line interface (CLI)&lt;/strong&gt; via YAML config files. Master's responsibility is to manage all the node events to keep the overall cluster configuration stable and healthy. The multiple components of master are as such:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kube-APIserver:&lt;/strong&gt; The front-end of kubernetes which exposes API calls and deals with REST services so developers can communicate with the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ETCD:&lt;/strong&gt; Manages state of the cluster. Try to look at it as the database of kubernetes. As a developer, you have to find a reliable method to backup these information in case of a master's crash. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kube-Scheduler:&lt;/strong&gt; As the name suggests, it schedules the newly created pods into the nodes based on the given resource requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kube-controller-manager:&lt;/strong&gt; consists of multiple controllers which manages the cluster and nodes. For example:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replication Controller:&lt;/strong&gt; handles the replicas (series of identical pods) &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;NODE:&lt;/strong&gt; Is a part of the kubernetes run-time environment and is hosted on a separate VM or physical machine than the master. Nodes have a series of pods inside of them which allow containers to run inside of them. Node responsibility is to monitor pods and report back to the master. Node's components consist of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pods:&lt;/strong&gt; A logical collection of containers (like &lt;em&gt;docker containers&lt;/em&gt;) which need to interact with each other for an application to work. Furthermore, Pods have series of &lt;code&gt;cgroup&lt;/code&gt;, &lt;code&gt;kernel space&lt;/code&gt;, &lt;code&gt;union file systems&lt;/code&gt; inside of them. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubelet:&lt;/strong&gt; Each node has a kubelet which makes sure required &lt;strong&gt;podSpects&lt;/strong&gt; are running healthy on the node. This agent can then report back to the master. their other tasks are to: 

&lt;ul&gt;
&lt;li&gt;Communicate With API server.&lt;/li&gt;
&lt;li&gt;Execute pods' containers via a container-engine. &lt;/li&gt;
&lt;li&gt;Mount and run pod-volumes and secrets. &lt;/li&gt;
&lt;li&gt;Execute health checks and report back to master.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Podspec: Is a YAML file that describes what each pod should have.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;blockquote&gt;
&lt;p&gt;Good to note that kubelets usually use an endpoint to communicate such as &lt;code&gt;tcp:10255&lt;/code&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kube-proxy:&lt;/strong&gt; Allows network traffic finds the right node and right pod inside (learn more about networking).&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope by now you have a high level understanding of different kubernete's components and their role to enable orchestration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cool beans, but how do I control and use all of these?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a devops specialist, you should be able to talk to this kubernetes beast by configuring certain &lt;strong&gt;controllers&lt;/strong&gt;. Therefore, you can spin up your cluster and run your containers reliably inside. &lt;/p&gt;

&lt;h2&gt;
  
  
  Controllers
&lt;/h2&gt;

&lt;p&gt;Controllers help with application reliability, scaling, and Load balancing. We need to understand different kinds of controllers within kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replicates:&lt;/strong&gt; Ensures the certain number of pods are running at all times. We use replica-sets to scale up and scale down the pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployments:&lt;/strong&gt; Declare updates for pods and replicates. Basically, a deployment manages replicates and replicates manage pods. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DaemonSets:&lt;/strong&gt; Ensures all nodes copy a specific pod. Monitoring the pods on the nodes to make sure there are all identical. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jobs:&lt;/strong&gt; Usually a supervisor process. Jobs are individual processes that happen once or periodically such as database backup. &lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Services:&lt;/strong&gt; Allow communication between a set of deployment. Services can be: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal: IP only reachable within cluster. &lt;/li&gt;
&lt;li&gt;External: Endpoint available from node ip.&lt;/li&gt;
&lt;li&gt;Load balancer: Expose the application. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Labels:&lt;/strong&gt; As the name suggests, it helps us to label and organize components. you can search, delete, and run services based on their labels. We use &lt;em&gt;selectors&lt;/em&gt; to find labels. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Selectors:&lt;/strong&gt; You can use them in two different method: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;equality-based selectors:  (==, !=)&lt;/li&gt;
&lt;li&gt;Set-based selectors: (IN , NOTIN, EXISTS)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Namespaces:&lt;/strong&gt; Give authentication and authorization to different users. extremely useful for bigger teams.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Congratulations! now you know the basic knowledge needed to start getting your hands dirty with the code...
&lt;/h4&gt;

&lt;p&gt;Next step .... &lt;strong&gt;code&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs" rel="noopener noreferrer"&gt;https://kubernetes.io/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/learning/learning-kubernetes" rel="noopener noreferrer"&gt;https://www.linkedin.com/learning/learning-kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
