<?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: Mike Elsmore</title>
    <description>The latest articles on DEV Community by Mike Elsmore (@ukmadlz).</description>
    <link>https://dev.to/ukmadlz</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F504284%2Fc4bc1cec-ce08-48da-9552-d9a032329042.png</url>
      <title>DEV Community: Mike Elsmore</title>
      <link>https://dev.to/ukmadlz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ukmadlz"/>
    <language>en</language>
    <item>
      <title>How to Stream Video with Just JavaScript and HTML5</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Mon, 02 Feb 2026 13:43:41 +0000</pubDate>
      <link>https://dev.to/ukmadlz/how-to-stream-video-with-just-javascript-and-html5-3gik</link>
      <guid>https://dev.to/ukmadlz/how-to-stream-video-with-just-javascript-and-html5-3gik</guid>
      <description>&lt;p&gt;If you're old enough to remember Internet Explorer, CSS shim files, and spending way too much time on Newgrounds, first off—how are your knees these days? But secondly, you probably remember &lt;strong&gt;Flash Player&lt;/strong&gt; and &lt;strong&gt;Silverlight&lt;/strong&gt; as being the way you watched video in web browsers. Those were the days of browser plugins, mysterious crashes, and that wonderful "Click to enable Flash" banner that haunted every second website.&lt;/p&gt;

&lt;p&gt;But since 2008, we've had the option to use the native &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; HTML5 element to handle video playback and streaming. No more plugins. No more Adobe update prompts. And here's the thing that might surprise you: we don't even need a modern framework to do this.&lt;/p&gt;

&lt;p&gt;The requirements to play with the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element are as simple as any web browser on a modern operating system, and an &lt;strong&gt;ImageKit&lt;/strong&gt; account to host your video files. We'll also cover some fun upgrades and extras you can do later on with JavaScript and ImageKit's video API.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it in action
&lt;/h2&gt;

&lt;p&gt;If you want to skip ahead and see the finished result, I've got you covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://elsmore.me/imagekit-javascript-video-streaming/" rel="noopener noreferrer"&gt;https://elsmore.me/imagekit-javascript-video-streaming/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/ukmadlz/imagekit-astro-video-streaming" rel="noopener noreferrer"&gt;https://github.com/ukmadlz/imagekit-astro-video-streaming&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to poke around the code, fork it, break it, and make it your own. Now let's walk through how it all works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Using the HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;Adding playback controls&lt;/li&gt;
&lt;li&gt;Defining multiple video sources&lt;/li&gt;
&lt;li&gt;Displaying a video poster (thumbnail)&lt;/li&gt;
&lt;li&gt;Auto-playing videos the right way&lt;/li&gt;
&lt;li&gt;Building a custom video player with JavaScript&lt;/li&gt;
&lt;li&gt;Adaptive Bitrate Streaming (ABS)&lt;/li&gt;
&lt;li&gt;Using Video.js for advanced streaming&lt;/li&gt;
&lt;li&gt;Optimising video delivery with ImageKit&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using the HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element
&lt;/h2&gt;

&lt;p&gt;To start displaying a video on any given webpage is as easy as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;An Example HTML5 Video&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt;
      &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt;
      &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
      &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's right—just like an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag takes a source location to display an image, the only thing the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag strictly &lt;em&gt;needs&lt;/em&gt; is a file passed into the &lt;code&gt;src&lt;/code&gt; attribute. And just like an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, it's best to assign both a &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; to the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element to avoid layout shifts as the video loads.&lt;/p&gt;

&lt;p&gt;If you're thinking "wait, that's it?"—yes, that's genuinely it for the bare minimum. But of course, we're going to make it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding playback controls
&lt;/h2&gt;

&lt;p&gt;That bare-bones example above will display a video, but your users won't be able to do much with it. They can't play, pause, adjust volume, or seek through the timeline. Let's fix that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;
  &lt;span class="na"&gt;controls&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;controls&lt;/code&gt; attribute adds the browser's default video controls. This includes play/pause, a progress bar, volume control, and fullscreen toggle. Each browser renders these slightly differently (Firefox, Chrome, and Safari all have their own flavour), but they're functional across the board.&lt;/p&gt;

&lt;p&gt;Here are some other useful attributes you'll want to know about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&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;controls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows play, pause, volume, progress bar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;autoplay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts playing automatically (with caveats)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;muted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts the video muted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loops the video when it ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;playsinline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plays inline on iOS instead of fullscreen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hints how much to preload (&lt;code&gt;none&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, &lt;code&gt;auto&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;poster&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows an image before the video plays&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Defining multiple video sources
&lt;/h2&gt;

&lt;p&gt;Not all browsers support all video formats. While MP4 (H.264) is nearly universal these days, WebM can offer better compression, and you might have legacy content in other formats. The &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; element lets you provide fallbacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.webm"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/webm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Your browser does not support the video tag.
&lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser reads top to bottom and picks the first format it supports. So if you want users to get WebM (smaller files, same quality), put that first. MP4 acts as the fallback.&lt;/p&gt;

&lt;p&gt;The text between the tags ("Your browser does not support...") only displays if the browser doesn't support the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element at all. This is increasingly rare, but it's good practice to include it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; With ImageKit, you can transform video formats on the fly using URL parameters. Instead of hosting multiple files, use a single source and let ImageKit handle the conversion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-webm"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/webm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-mp4"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Displaying a video poster (thumbnail)
&lt;/h2&gt;

&lt;p&gt;Before your video loads, you'll want to show something other than a black rectangle. The &lt;code&gt;poster&lt;/code&gt; attribute lets you display an image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;
  &lt;span class="na"&gt;controls&lt;/span&gt;
  &lt;span class="na"&gt;poster=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ImageKit has a neat trick here: you can generate thumbnails automatically from your video using the &lt;code&gt;/ik-thumbnail.jpg&lt;/code&gt; suffix. Want a thumbnail from a specific timestamp? Add a transformation parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This grabs a frame from 5 seconds into the video. You can also resize it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-5,w-640,h-360
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to manually create and host poster images—let the API do the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-playing videos the right way
&lt;/h2&gt;

&lt;p&gt;Autoplay is a minefield. Browsers have cracked down hard on autoplaying videos because nobody likes visiting a site and having sound blast at them unexpectedly. Here's how to do it properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;
  &lt;span class="na"&gt;autoplay&lt;/span&gt;
  &lt;span class="na"&gt;muted&lt;/span&gt;
  &lt;span class="na"&gt;playsinline&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;autoplay&lt;/code&gt; alone won't work in most browsers&lt;/strong&gt; - they require the video to also be &lt;code&gt;muted&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;playsinline&lt;/code&gt; is essential for iOS&lt;/strong&gt; - without it, Safari on mobile opens videos in fullscreen mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider your users&lt;/strong&gt; - autoplaying videos (even muted) can be jarring&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern is perfect for hero videos, background loops, or product demos where you don't need audio. If you do need audio, you'll need to wait for user interaction first (like a click) before calling &lt;code&gt;video.play()&lt;/code&gt; in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a custom video player with JavaScript
&lt;/h2&gt;

&lt;p&gt;The native controls are fine, but sometimes you want something custom. The HTML5 video element exposes a comprehensive JavaScript API for building your own player. Here's a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Custom Video Player&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;.video-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.controls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#222&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.controls&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.progress-bar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;flex-grow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#555&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.progress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4CAF50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.time&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"video-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"video"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"controls"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"playPause"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Play&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"progress-bar"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"progressBar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"time"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"time"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0:00 / 0:00&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playPauseBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playPause&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progressBar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;progressBar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeDisplay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Play/Pause toggle&lt;/span&gt;
    &lt;span class="nx"&gt;playPauseBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;playPauseBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pause&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;playPauseBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Play&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Update progress bar&lt;/span&gt;
    &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeupdate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;timeDisplay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formatTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; / &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;formatTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Click to seek&lt;/span&gt;
    &lt;span class="nx"&gt;progressBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;progressBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Format seconds to M:SS&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0:00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mins&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;secs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The video element gives you access to a wealth of properties and methods:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Properties:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;video.currentTime&lt;/code&gt; - current playback position in seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video.duration&lt;/code&gt; - total duration in seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video.paused&lt;/code&gt; - boolean indicating if video is paused&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video.volume&lt;/code&gt; - volume level from 0.0 to 1.0&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video.playbackRate&lt;/code&gt; - playback speed (1.0 is normal)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Methods:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;video.play()&lt;/code&gt; - start playback (returns a Promise)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video.pause()&lt;/code&gt; - pause playback&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;video.load()&lt;/code&gt; - reload the video source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Events:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;play&lt;/code&gt; - fired when playback starts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pause&lt;/code&gt; - fired when playback pauses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timeupdate&lt;/code&gt; - fired as playback progresses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ended&lt;/code&gt; - fired when video reaches the end&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loadedmetadata&lt;/code&gt; - fired when duration/dimensions are available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you everything you need to build players that match your brand, add custom features, or integrate with your application's state management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adaptive Bitrate Streaming (ABS)
&lt;/h2&gt;

&lt;p&gt;Here's where things get interesting. So far, we've been dealing with single video files—one quality level, one file size, one experience for everyone. But what happens when someone's on a dodgy coffee shop WiFi versus a fibre connection at home?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adaptive Bitrate Streaming (ABS)&lt;/strong&gt; solves this by encoding your video at multiple quality levels and letting the player switch between them based on network conditions. The video starts quickly at lower quality, then upgrades as bandwidth allows. If the connection drops, it downgrades gracefully instead of buffering endlessly.&lt;/p&gt;

&lt;p&gt;There are two main protocols:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HLS (HTTP Live Streaming)&lt;/strong&gt; - Apple's format, uses &lt;code&gt;.m3u8&lt;/code&gt; playlist files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MPEG-DASH (Dynamic Adaptive Streaming over HTTP)&lt;/strong&gt; - Uses &lt;code&gt;.mpd&lt;/code&gt; manifest files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The catch? The native &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element doesn't support these formats directly in most browsers. Safari supports HLS natively (it's Apple's thing, after all), but Chrome and Firefox don't. That's where JavaScript libraries come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Video.js for advanced streaming
&lt;/h2&gt;

&lt;p&gt;Video.js is an open-source HTML5 video player that handles ABS protocols, provides a consistent UI across browsers, and offers a plugin ecosystem for extended functionality.&lt;/p&gt;

&lt;p&gt;First, include Video.js in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Video.js Streaming Example&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://vjs.zencdn.net/8.10.0/video-js.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"my-video"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"video-js vjs-default-skin"&lt;/span&gt;
    &lt;span class="na"&gt;controls&lt;/span&gt;
    &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
    &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/demo/sample-video.mp4/ik-master.mpd?tr=sr-240_360_480_720_1080"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/dash+xml"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://vjs.zencdn.net/8.10.0/video.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;videojs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fluid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Responsive sizing&lt;/span&gt;
      &lt;span class="na"&gt;playbackRates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.5&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="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Speed options&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For HLS streams, just swap the source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/demo/sample-video.mp4/ik-master.m3u8?tr=sr-240_360_480_720_1080"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/x-mpegURL"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Video.js (version 7+) handles both DASH and HLS out of the box, no additional plugins required. The player will automatically select the appropriate quality based on the viewer's bandwidth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality selection
&lt;/h3&gt;

&lt;p&gt;Video.js also lets users manually select quality levels if they prefer. You can add this functionality with the quality selector plugin, or let the player handle it automatically—most users won't need to touch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimising video delivery with ImageKit
&lt;/h2&gt;

&lt;p&gt;I've been dropping ImageKit URLs throughout this post, so let's properly cover what it can do for your video streaming setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resize videos to appropriate dimensions
&lt;/h3&gt;

&lt;p&gt;Why send a 4K video to someone watching on a 400px container? ImageKit lets you resize on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4?tr=w-640,h-360
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't just scale the player—it actually delivers a smaller file. A video at the original 4K resolution might be 20MB, while the same video resized to 640x360 could be under 2MB. Same visual quality for your use case, massively reduced bandwidth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic format selection
&lt;/h3&gt;

&lt;p&gt;Instead of manually specifying WebM and MP4 sources, let ImageKit figure out what the browser supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-auto"&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;f-auto&lt;/code&gt; transformation detects the viewer's browser and delivers the most optimised format automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate ABS playlists
&lt;/h3&gt;

&lt;p&gt;Creating DASH and HLS playlists normally requires encoding your video at multiple bitrates, generating segment files, and creating manifest files. ImageKit does all of this from a single source video:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DASH:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.mpd?tr=sr-240_360_480_720_1080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;HLS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.m3u8?tr=sr-240_360_480_720_1080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sr-240_360_480_720_1080&lt;/code&gt; parameter specifies which quality levels to include. ImageKit handles the encoding and playlist generation on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add overlays
&lt;/h3&gt;

&lt;p&gt;Need to watermark your videos or add captions? ImageKit supports text and image overlays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4?tr=l-text,i-My%20Company,fs-40,co-white,bg-00000080,pa-20,l-end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds "My Company" as a text overlay with a semi-transparent black background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Video streaming with JavaScript and HTML5 has come a long way since the Flash days. The native &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element handles most common use cases without any external dependencies. When you need adaptive streaming for varying network conditions, Video.js provides a solid foundation that works across browsers.&lt;/p&gt;

&lt;p&gt;The real power comes from combining these client-side capabilities with a smart CDN like ImageKit. Instead of pre-encoding dozens of video variants, you can transform and deliver optimised video on the fly. Format conversion, resizing, thumbnail generation, and adaptive streaming playlists all happen automatically from a single source file.&lt;/p&gt;

&lt;p&gt;Whether you're building a simple product demo or a full video streaming platform, the tools are all there in the browser—we just need to use them.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Choosing Your Documentation Tooling: A Practical Guide</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Tue, 27 Jan 2026 16:47:56 +0000</pubDate>
      <link>https://dev.to/digitalspeed/choosing-your-documentation-tooling-a-practical-guide-1po1</link>
      <guid>https://dev.to/digitalspeed/choosing-your-documentation-tooling-a-practical-guide-1po1</guid>
      <description>&lt;p&gt;In our &lt;a href="https://digitalspeed.co.uk/articles/the-differentiator-between-b2b-products-the-developer-experience" rel="noopener noreferrer"&gt;previous post on Developer Experience&lt;/a&gt;, we touched on documentation being one of the core pillars of great DX. We mentioned treating your docs as a product rather than a necessary evil, and pointed towards the docs-as-code approach. But we didn't dive into the specifics of what to actually build your documentation with. So let's fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Landscape 
&lt;/h2&gt;

&lt;p&gt;There's no shortage of documentation tools out there, and honestly, that can make the decision harder rather than easier. After working with various clients and our own projects here at Digital Speed, we've found ourselves reaching for a handful of tools repeatedly: &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt;, &lt;a href="https://vuepress.vuejs.org/" rel="noopener noreferrer"&gt;VuePress&lt;/a&gt;, &lt;a href="https://redocly.com/" rel="noopener noreferrer"&gt;Redocly&lt;/a&gt;, and &lt;a href="https://www.fumadocs.dev/" rel="noopener noreferrer"&gt;Fumadocs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each has its strengths, and sometimes the best solution isn't picking one – it's knowing how they can work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docusaurus 
&lt;/h2&gt;

&lt;p&gt;If you've spent any time in the open-source world, you've probably read documentation built with &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt;. Meta's documentation framework has become something of a default choice, and for good reason.&lt;/p&gt;

&lt;p&gt;Docusaurus is React-based, which means if your team already lives in that ecosystem, the learning curve is minimal. It handles versioning out of the box (a genuine lifesaver when you're maintaining multiple API versions), has solid search via Algolia integration, and the plugin ecosystem is mature enough that most common needs are covered.&lt;/p&gt;

&lt;p&gt;Several members of the team here at Digital Speed have used Docusaurus extensively, and what keeps us coming back is the balance it strikes between flexibility and convention. You can get something professional-looking deployed in an afternoon, but you're not boxed in when requirements get more complex down the line.&lt;/p&gt;

&lt;p&gt;The main consideration? It's opinionated about structure, which is actually a feature if you're starting fresh but can be friction if you're migrating existing docs with unusual organisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  VuePress 
&lt;/h2&gt;

&lt;p&gt;For teams in the Vue ecosystem, &lt;a href="https://vuepress.vuejs.org/" rel="noopener noreferrer"&gt;VuePress&lt;/a&gt; is the natural counterpart to Docusaurus. It's lightweight, fast, and the Vue-powered theming system makes customisation straightforward if you know your way around Vue components.&lt;/p&gt;

&lt;p&gt;VuePress 2 (the current version) has matured significantly and offers a clean developer experience. The markdown-centred approach means your content stays portable, and the plugin architecture is sensible without being overwhelming.&lt;/p&gt;

&lt;p&gt;We've seen VuePress work particularly well for smaller documentation sites or teams who want something less heavyweight than Docusaurus. If you're documenting a Vue-based library or your team simply prefers Vue's patterns, it's worth serious consideration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redocly 
&lt;/h2&gt;

&lt;p&gt;Now we're getting into more specialised territory. &lt;a href="https://redocly.com/" rel="noopener noreferrer"&gt;Redocly&lt;/a&gt; is laser-focused on API documentation, specifically OpenAPI-based docs. If you're building products with REST APIs (and let's be honest, most of us are), Redocly deserves a look.&lt;/p&gt;

&lt;p&gt;The open-source Redoc renderer produces three-panel API documentation from your OpenAPI spec. It's the kind of documentation that makes developers actually want to integrate with your API. We've used the open-source version at Digital Speed and found it does exactly what it promises with minimal fuss. The only issue with the open-source version is you're pretty much locked in to the limited out-of-the-box styling and theming options, so you can't expect to customise the design much.&lt;/p&gt;

&lt;p&gt;Beyond the renderer, Redocly offers a CLI for linting your OpenAPI specs and a broader platform for teams who need more – but the open-source tools alone cover a lot of ground.&lt;/p&gt;

&lt;p&gt;The key thing to understand about Redocly is that it's not trying to be a general-purpose documentation platform. It's purpose-built for API reference documentation, and it excels at that specific job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fumadocs 
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.fumadocs.dev/" rel="noopener noreferrer"&gt;Fumadocs&lt;/a&gt; is the newer entrant here, built specifically for the &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; ecosystem. If you're already running Next.js (and plenty of teams are these days), Fumadocs integrates naturally into your existing setup rather than being a separate concern. &lt;/p&gt;

&lt;p&gt;We've recently started using Fumadocs at Digital Speed and have been impressed by how it handles the basics. The search works well, the theming is highly customisable and clean, and it doesn't fight against Next.js conventions – it embraces them.&lt;/p&gt;

&lt;p&gt;It's worth noting that Fumadocs is younger than the other options, which means the ecosystem is smaller and you might occasionally hit edges that haven't been smoothed yet. Additionally, the documentation is directly tied to Next.js versions and isn't versioned itself, so you might not be able to find documentation for older versions of Next.js. But for Next.js teams, it's a compelling option that's maturing quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Tools in Concert 
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. These tools don't have to be mutually exclusive. &lt;/p&gt;

&lt;p&gt;A pattern we've seen work well is using Docusaurus (or Fumadocs, depending on your stack) as the primary documentation platform for guides, tutorials, and conceptual content – then embedding Redocly-generated API reference documentation within it.&lt;/p&gt;

&lt;p&gt;This gives you the best of both worlds: a flexible, customisable platform for your narrative documentation, paired with purpose-built tooling for your API reference. Your developers get a cohesive experience, but you're using the right tool for each job.&lt;/p&gt;

&lt;p&gt;The integration is typically straightforward. Redocly can generate static HTML that you host alongside your main docs, or you can embed the Redoc React component directly if you're in a React-based framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Choice 
&lt;/h2&gt;

&lt;p&gt;So how do you actually decide? A few questions worth asking:&lt;/p&gt;

&lt;p&gt;What's your team's existing stack? If you're React-heavy, Docusaurus or Fumadocs (for Next.js specifically) will feel natural. Vue teams should look at VuePress.&lt;/p&gt;

&lt;p&gt;Do you have OpenAPI specs to document? Strongly consider Redocly for that portion of your docs, even if you use something else for the rest.&lt;/p&gt;

&lt;p&gt;How much customisation do you need? All of these tools are customisable, but they sit on a spectrum from "works great with defaults" to "expects you to build on top." Know where your needs fall.&lt;/p&gt;

&lt;p&gt;What's your team's capacity? A smaller team might prefer something lightweight like VuePress or Fumadocs over Docusaurus's broader feature set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 
&lt;/h2&gt;

&lt;p&gt;There's no single "best" documentation tool – there's only the best tool for your specific situation. What matters more than the specific choice is that you make a choice and invest in your documentation as a proper product. &lt;/p&gt;

&lt;p&gt;We'll be covering more specifics around SDK development and API documentation in upcoming posts. In the meantime, if you're struggling with your documentation strategy or need help implementing any of these tools, &lt;a href="https://digitalspeed.co.uk/contact" rel="noopener noreferrer"&gt;we at Digital Speed are happy to chat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After all, great documentation is just another way of helping developers get things done.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>devex</category>
      <category>programming</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Add Video to Your Astro Site with ImageKit</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Thu, 22 Jan 2026 16:33:43 +0000</pubDate>
      <link>https://dev.to/ukmadlz/how-to-add-video-to-your-astro-site-with-imagekit-1p3m</link>
      <guid>https://dev.to/ukmadlz/how-to-add-video-to-your-astro-site-with-imagekit-1p3m</guid>
      <description>&lt;p&gt;If you've been anywhere near web development Twitter (or X, or Bluesky, or whatever we're calling it this week) in the last few days, you've probably seen the news: Cloudflare has acquired The Astro Technology Company. The team behind one of the most interesting web frameworks to emerge in recent years is now part of the connectivity cloud giant.&lt;/p&gt;

&lt;p&gt;For those of us who've been building with Astro, this is genuinely exciting news. Not "exciting" in the Silicon Valley way where a company gets acqui-hired and the product slowly dies, but actual excitement. Cloudflare has committed to keeping Astro open source, MIT-licensed, and platform-agnostic. Fred Schott, Astro's CEO, wrote that joining Cloudflare means the team can finally stop worrying about building a business on top of Astro and focus 100% on the framework itself.&lt;/p&gt;

&lt;p&gt;The timing is particularly interesting because Astro 6 just hit beta, bringing a redesigned development server, better runtime support, and improved performance. With Cloudflare's resources behind it, Astro is positioned to become the definitive framework for content-driven websites—and content-driven websites almost always need video.&lt;/p&gt;

&lt;p&gt;So let's talk about how to implement video in Astro, specifically using ImageKit's video API to handle the heavy lifting of transcoding, streaming, and optimisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it in action
&lt;/h2&gt;

&lt;p&gt;If you'd rather jump straight to the code or see the finished result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo&lt;/strong&gt;: &lt;a href="https://elsmore.me/imagekit-astro-video-streaming/" rel="noopener noreferrer"&gt;https://elsmore.me/imagekit-astro-video-streaming/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code&lt;/strong&gt;: &lt;a href="https://github.com/ukmadlz/imagekit-astro-video-streaming" rel="noopener noreferrer"&gt;https://github.com/ukmadlz/imagekit-astro-video-streaming&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clone it, deploy it, tear it apart—it's all there for you to play with. Now let's dig into the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Astro for video content?
&lt;/h2&gt;

&lt;p&gt;Astro was born in 2021 out of frustration with a web that had become obsessed with shipping JavaScript to the browser for everything. The prevailing wisdom was that every website should be architected as an application—rendered client-side, hydrated, and heavy with JavaScript bundles.&lt;/p&gt;

&lt;p&gt;Astro took a different approach: &lt;strong&gt;ship HTML by default, JavaScript only when necessary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This philosophy—what Astro calls "Islands Architecture"—is perfect for video-heavy content sites. Your video player can be an interactive island while the rest of your page (navigation, text content, footer) remains static HTML. The result? Faster initial page loads, better Core Web Vitals scores, and happier search engines.&lt;/p&gt;

&lt;p&gt;Sites like Porsche, IKEA, and OpenAI use Astro. Platforms like Webflow and Wix have built on it. And now with Cloudflare's backing, performance-focused content sites have a framework with serious long-term support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up an Astro project
&lt;/h2&gt;

&lt;p&gt;Let's start from scratch. If you've got Node.js 18.17.0 or later installed, creating a new Astro project is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest my-video-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI will ask you a few questions. For this tutorial, I'd recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template&lt;/strong&gt;: Empty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt;: Yes (strict)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies&lt;/strong&gt;: Yes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Navigate into your project and start the development server:&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;my-video-site
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your site is now running at &lt;a href="http://localhost:4321/" rel="noopener noreferrer"&gt;http://localhost:4321/&lt;/a&gt;. Yes, 4321—Astro counts down rather than up. It's a small thing, but I appreciate the personality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic video with HTML5
&lt;/h2&gt;

&lt;p&gt;The simplest way to add video to an Astro page is exactly the same as any HTML page—the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element just works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/pages/index.astro
---
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Video Demo&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Welcome to my video site&amp;lt;/h1&amp;gt;
    &amp;lt;video
      src="https://ik.imagekit.io/ikmedia/example_video.mp4"
      width="640"
      height="360"
      controls
    &amp;gt;
      Your browser does not support the video tag.
    &amp;lt;/video&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No JavaScript shipped to the client. No framework overhead. Just HTML and a video file.&lt;/p&gt;

&lt;p&gt;Want multiple format sources for browser compatibility? Same as regular HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-webm"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/webm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-mp4"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Your browser does not support the video tag.
&lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice those URL parameters? That's ImageKit transforming the video format on the fly. One source file, multiple formats, no manual transcoding required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a reusable Video component
&lt;/h2&gt;

&lt;p&gt;In Astro, components are &lt;code&gt;.astro&lt;/code&gt; files with a frontmatter section (between the &lt;code&gt;---&lt;/code&gt; fences) and a template section. Let's create a reusable Video component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/components/Video.astro
interface Props {
  src: string;
  width?: number;
  height?: number;
  poster?: string;
  controls?: boolean;
  autoplay?: boolean;
  muted?: boolean;
  loop?: boolean;
  class?: string;
}

const {
  src,
  width = 640,
  height = 360,
  poster,
  controls = true,
  autoplay = false,
  muted = false,
  loop = false,
  class: className,
} = Astro.props;
---

&amp;lt;video
  src={src}
  width={width}
  height={height}
  poster={poster}
  controls={controls}
  autoplay={autoplay}
  muted={muted}
  loop={loop}
  playsinline
  class={className}
&amp;gt;
  &amp;lt;slot&amp;gt;Your browser does not support the video tag.&amp;lt;/slot&amp;gt;
&amp;lt;/video&amp;gt;

&amp;lt;style&amp;gt;
  video {
    max-width: 100%;
    height: auto;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can use it anywhere in your site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/pages/index.astro
import Video from '../components/Video.astro';
---
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Video Demo&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Product Demo&amp;lt;/h1&amp;gt;
    &amp;lt;Video
      src="https://ik.imagekit.io/ikmedia/example_video.mp4"
      poster="https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-3"
      width={800}
      height={450}
    /&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component is pre-rendered at build time. Zero JavaScript sent to the browser. The video element works natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Video.js for advanced playback
&lt;/h2&gt;

&lt;p&gt;Native HTML5 video covers most use cases, but sometimes you need more. Custom controls, adaptive bitrate streaming, analytics integration—this is where Video.js comes in.&lt;/p&gt;

&lt;p&gt;The catch with Astro is that Video.js needs to run in the browser. This is where Astro's Islands Architecture shines—we can add interactivity only where we need it.&lt;/p&gt;

&lt;p&gt;First, install Video.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install video.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a Video.js component. Since Video.js needs client-side JavaScript, we'll use a framework integration. Astro supports React, Vue, Svelte, Solid, and more. Let's use vanilla JavaScript with Astro's &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/components/VideoPlayer.astro
interface Props {
  src: string;
  poster?: string;
  width?: number;
  height?: number;
}

const { src, poster, width = 640, height = 360 } = Astro.props;
const playerId = `video-${Math.random().toString(36).substr(2, 9)}`;
---

&amp;lt;div class="video-player-wrapper"&amp;gt;
  &amp;lt;video
    id={playerId}
    class="video-js vjs-default-skin"
    controls
    preload="auto"
    width={width}
    height={height}
    poster={poster}
    data-src={src}
  &amp;gt;
    &amp;lt;source src={src} type="video/mp4" /&amp;gt;
  &amp;lt;/video&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  import videojs from 'video.js';
  import 'video.js/dist/video-js.css';

  // Initialize all video players on the page
  document.querySelectorAll('.video-js').forEach((element) =&amp;gt; {
    if (element instanceof HTMLVideoElement &amp;amp;&amp;amp; !element.hasAttribute('data-vjs-player')) {
      const player = videojs(element, {
        fluid: true,
        playbackRates: [0.5, 1, 1.5, 2],
      });

      // Cleanup on navigation (for Astro view transitions)
      document.addEventListener('astro:before-swap', () =&amp;gt; {
        player.dispose();
      });
    }
  });
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
  .video-player-wrapper {
    max-width: 100%;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag in Astro components runs on the client. By importing Video.js here, Astro bundles it and ships it only when this component is used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adaptive Bitrate Streaming in Astro
&lt;/h2&gt;

&lt;p&gt;For video-heavy sites, Adaptive Bitrate Streaming (ABS) is essential. It delivers different quality levels based on the viewer's network conditions—high quality on fast connections, lower quality (but still watchable) on slow ones.&lt;/p&gt;

&lt;p&gt;Video.js handles both HLS and DASH protocols out of the box. Here's how to set it up with ImageKit's automatically generated streaming playlists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/components/StreamingPlayer.astro
interface Props {
  src: string;
  poster?: string;
  title?: string;
}

const { src, poster, title = 'Video' } = Astro.props;

// Generate HLS and DASH URLs from the source
const hlsUrl = `${src}/ik-master.m3u8?tr=sr-240_360_480_720_1080`;
const dashUrl = `${src}/ik-master.mpd?tr=sr-240_360_480_720_1080`;
---

&amp;lt;div class="streaming-player"&amp;gt;
  &amp;lt;video
    id="streaming-video"
    class="video-js vjs-default-skin vjs-big-play-centered"
    controls
    preload="auto"
    poster={poster}
    data-hls={hlsUrl}
    data-dash={dashUrl}
  &amp;gt;
    &amp;lt;!-- HLS source (Safari native, others via Video.js) --&amp;gt;
    &amp;lt;source src={hlsUrl} type="application/x-mpegURL" /&amp;gt;
    &amp;lt;!-- Fallback MP4 for older browsers --&amp;gt;
    &amp;lt;source src={`${src}?tr=f-mp4`} type="video/mp4" /&amp;gt;
  &amp;lt;/video&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  import videojs from 'video.js';
  import 'video.js/dist/video-js.css';

  const videoElement = document.getElementById('streaming-video') as HTMLVideoElement;

  if (videoElement) {
    const player = videojs(videoElement, {
      fluid: true,
      html5: {
        vhs: {
          overrideNative: true, // Use Video.js HLS instead of native
        },
      },
    });

    // Quality selector (if you add the plugin)
    player.on('loadedmetadata', () =&amp;gt; {
      console.log('Stream loaded:', player.currentSource());
    });

    document.addEventListener('astro:before-swap', () =&amp;gt; {
      player.dispose();
    });
  }
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
  .streaming-player {
    max-width: 100%;
    margin: 0 auto;
  }

  .video-js {
    width: 100%;
    aspect-ratio: 16 / 9;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic here is in the ImageKit URLs. The &lt;code&gt;/ik-master.m3u8&lt;/code&gt; suffix tells ImageKit to generate an HLS playlist, while &lt;code&gt;tr=sr-240_360_480_720_1080&lt;/code&gt; specifies which quality levels to include. ImageKit handles all the transcoding and segment generation automatically.&lt;/p&gt;

&lt;p&gt;Use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import StreamingPlayer from '../components/StreamingPlayer.astro';
---
&amp;lt;StreamingPlayer
  src="https://ik.imagekit.io/demo/sample-video.mp4"
  poster="https://ik.imagekit.io/demo/sample-video.mp4/ik-thumbnail.jpg"
  title="Product Demo"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optimising video with ImageKit
&lt;/h2&gt;

&lt;p&gt;ImageKit's video API does more than just serve files. Here are the transformations that make the biggest difference for Astro sites:&lt;/p&gt;

&lt;h3&gt;
  
  
  Resize to match your layout
&lt;/h3&gt;

&lt;p&gt;Don't send 4K video to a 640px container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4?tr=w-640,h-360
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transformation happens on ImageKit's servers. Your users download a smaller file, your bandwidth bills go down, and videos start playing faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic format selection
&lt;/h3&gt;

&lt;p&gt;Let ImageKit figure out the best format for each browser:&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;Video src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-auto" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebM for browsers that support it (better compression), MP4 for everything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate thumbnails from the video
&lt;/h3&gt;

&lt;p&gt;No need to create and host separate poster images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-5,w-640,h-360
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This grabs a frame from 5 seconds into the video, resized to 640x360. Perfect for poster images and video galleries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add watermarks and overlays
&lt;/h3&gt;

&lt;p&gt;Protect your content or add branding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://ik.imagekit.io/ikmedia/example_video.mp4?tr=l-text,i-My%20Brand,fs-32,co-white,l-end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lazy loading and performance
&lt;/h3&gt;

&lt;p&gt;For pages with multiple videos, you don't want them all loading at once. Here's a lazy-loading video component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/components/LazyVideo.astro
interface Props {
  src: string;
  poster?: string;
  width?: number;
  height?: number;
}

const { src, poster, width = 640, height = 360 } = Astro.props;

// Generate a low-quality poster if none provided
const posterUrl = poster || `${src}/ik-thumbnail.jpg?tr=w-${width},h-${height},q-50`;
---

&amp;lt;div class="lazy-video" data-src={src} style={`aspect-ratio: ${width}/${height};`}&amp;gt;
  &amp;lt;img
    src={posterUrl}
    alt="Video thumbnail"
    loading="lazy"
    width={width}
    height={height}
  /&amp;gt;
  &amp;lt;button class="play-button" aria-label="Play video"&amp;gt;
    &amp;lt;svg viewBox="0 0 24 24" fill="currentColor" width="48" height="48"&amp;gt;
      &amp;lt;path d="M8 5v14l11-7z"/&amp;gt;
    &amp;lt;/svg&amp;gt;
  &amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
  document.querySelectorAll('.lazy-video').forEach((container) =&amp;gt; {
    const button = container.querySelector('.play-button');
    const img = container.querySelector('img');

    button?.addEventListener('click', () =&amp;gt; {
      const src = container.getAttribute('data-src');
      if (!src) return;

      const video = document.createElement('video');
      video.src = src;
      video.controls = true;
      video.autoplay = true;
      video.style.width = '100%';
      video.style.height = '100%';

      container.innerHTML = '';
      container.appendChild(video);
    });
  });
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
  .lazy-video {
    position: relative;
    background: #000;
    cursor: pointer;
  }

  .lazy-video img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .play-button {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: rgba(0, 0, 0, 0.7);
    border: none;
    border-radius: 50%;
    padding: 16px;
    color: white;
    cursor: pointer;
    transition: background 0.2s;
  }

  .play-button:hover {
    background: rgba(0, 0, 0, 0.9);
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows a thumbnail and play button. The actual video only loads when clicked—no wasted bandwidth on videos users never watch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persisting video across page transitions
&lt;/h2&gt;

&lt;p&gt;Astro 3+ introduced View Transitions, which enable smooth animations between pages. One neat feature: you can persist elements across navigations.&lt;/p&gt;

&lt;p&gt;If you have a video playing and the user navigates to another page with the same video, you can keep it playing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://ik.imagekit.io/ikmedia/example_video.mp4"&lt;/span&gt;
  &lt;span class="na"&gt;controls&lt;/span&gt;
  &lt;span class="na"&gt;transition:persist&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;transition:persist&lt;/code&gt; directive tells Astro to keep this element instead of replacing it during navigation. The video continues playing without interruption.&lt;/p&gt;

&lt;p&gt;This is fantastic for sites with persistent media players—podcasts, background music, or video courses where you don't want playback to stop as users navigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Astro's philosophy of shipping minimal JavaScript makes it an excellent choice for video content sites. The framework gives you the performance benefits of static HTML while allowing interactive islands—like video players—exactly where you need them.&lt;/p&gt;

&lt;p&gt;With Cloudflare's acquisition, Astro's future looks more stable than ever. The team can focus entirely on the framework without the distraction of building a business model. For those of us building content-driven sites with video, that's genuinely good news.&lt;/p&gt;

&lt;p&gt;Combine Astro's architecture with ImageKit's video API, and you get a powerful setup: automatic format optimisation, on-the-fly transcoding, adaptive bitrate streaming, and thumbnail generation—all without maintaining your own video infrastructure.&lt;/p&gt;

&lt;p&gt;The web doesn't have to be slow. It doesn't have to ship megabytes of JavaScript for every page load. And video doesn't have to be complicated.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Differentiator Between B2B products: The Developer Experience</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Wed, 21 Jan 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/digitalspeed/the-differentiator-between-b2b-products-the-developer-experience-1obd</link>
      <guid>https://dev.to/digitalspeed/the-differentiator-between-b2b-products-the-developer-experience-1obd</guid>
      <description>&lt;p&gt;Here at Digital Speed we are active users of AI within our day to day, a lot of us enjoy using Claude Code as part of our tool chain and we even use &lt;a href="https://digitalspeed.co.uk/articles/spec-driven-development" rel="noopener noreferrer"&gt;Spec Kit&lt;/a&gt; within some projects, and we can see how it has improved our pace and work.&lt;/p&gt;

&lt;p&gt;But the velocity created by the tools we have to hand mean that everything is starting to feel a bit samey when integrating products together. So that’s the biggest separator between the products we’ve used recently? The Developer Experience, otherwise known as DX or DevX. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Developer Experience? 
&lt;/h2&gt;

&lt;p&gt;Most people see DX and think UX, which is close but not quite right. The dictionary definition of Developer Experience from Wikipedia is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Developer experience (DX) is a user experience from a developer's point of view. It is defined by the tools, processes, and software that a developer uses when interacting with a product or system while in the process of production of another one, such as in software development.[23] DX has had increased attention paid to it especially in businesses who primarily offer software as a service to other businesses where ease of use is a key differentiator in the market." &lt;br&gt;
Some people on Wikipedia - &lt;a href="https://en.wikipedia.org/wiki/User_experience#Developer_experience" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/User_experience#Developer_experience&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A much nice definition is this quote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"DevX is about creating frisson with your product. It's about creating thrill, feelings of excitement, and enabling and empowering developers to believe, 'Hey, I can do that.'" &lt;br&gt;
&lt;a href="https://www.linkedin.com/in/whitep4nth3r/" rel="noopener noreferrer"&gt;Salma Alam-Naylor, @whitep4nth3r&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to watch a video covering the subject, our very own &lt;a href="https://www.wearedevelopers.com/en/videos/727/the-abc-of-dx" rel="noopener noreferrer"&gt;Mike Elsmore gave a talk at WeAreDevelopers&lt;/a&gt;. But what are the core things to consider when thinking about your product's Developer Experience? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation &lt;/li&gt;
&lt;li&gt;Tooling &lt;/li&gt;
&lt;li&gt;Support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll be covering some of these specific topics in follow-up posts, but for now here is a quick overview of the things to consider.&lt;/p&gt;

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

&lt;p&gt;When moving at speed, most organisations don’t have time to keep documentation up-to-date. The biggest advice that can be given here is treating your documentation as a product, rather than as a necessary evil.&lt;/p&gt;

&lt;p&gt;If you are a tech first organisation, then one of the best things that can be done is a shift to docs-as-code. An amazing article on this topic written by &lt;a href="https://www.linkedin.com/in/joannasuau/" rel="noopener noreferrer"&gt;Joanna Suau&lt;/a&gt; covers all this in greater detail: &lt;a href="https://medium.com/design-bootcamp/how-to-adopt-a-docs-as-code-approach-for-developer-documentation-59d63e822c5a" rel="noopener noreferrer"&gt;how to adopt a docs-as-code approach for developer documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As for what should be used for documentation, or what should be covered, this is very much dependant on the product. And we will cover this more in depth in a follow-up article. However, just as something to think about in the mean time if you are providing technology with an API interface that requires a complete reference and should follow a standard like &lt;a href="https://www.openapis.org/" rel="noopener noreferrer"&gt;OpenAPI Specification&lt;/a&gt;. On top of this a glossary of all possible errors from the API or tool should be available alongside it. &lt;/p&gt;

&lt;p&gt;And the last things on documentation, make sure it’s written from the perspective of someone that has no knowledge or prior skills. &lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling 
&lt;/h2&gt;

&lt;p&gt;When it comes to how developers interact with your product, it can differ widely. But the common consensus is to provide a faster and smoother way to integrate your product with whatever they’re building in whatever language or system they are building it within. &lt;/p&gt;

&lt;p&gt;The most direct way, after documenting the basics for any interaction, is through an SDK. SDKs can consist of things such as API wrapper clients, to full CLI tooling, but they all contain the pieces to simplify integrating things such as authentication/authorization. We’ll cover more of the specifics of how to build out SDKs (primarily Client Wrappers) in a follow up post, but for now I’ll present some of the best examples. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://fly.io/docs/flyctl/" rel="noopener noreferrer"&gt;Fly.io CLI&lt;/a&gt; (otherwise known as &lt;code&gt;flyctl&lt;/code&gt;), is a great example of a command line interface that would allow someone to interact with the product directly or even script out into CI/CD flows. &lt;/p&gt;

&lt;p&gt;A great example of a full-service SDK has to be the &lt;a href="https://aws.amazon.com/sdk-for-javascript/" rel="noopener noreferrer"&gt;AWS SDK&lt;/a&gt; (in any language they build for). Considering how vast the product array is across the AWS portfolio, it’s consistent and easy to navigate with a lot of documentation and all sorts of helper functionality on top of the APIs. &lt;/p&gt;

&lt;p&gt;And now in the age of AI, it’s worth considering MCP servers and the likes, just as a means to allow developers to build agents to interact with your product. Or at the very least, for AI tooling to have a better understanding of the product when developers are doing research or solving a problem. &lt;/p&gt;

&lt;h2&gt;
  
  
  Support 
&lt;/h2&gt;

&lt;p&gt;Unfortunately, this one comes very much down to business and financial budgets. Support tooling and time to manage supporting your end users is a large part of great developer experience. No matter how much documentation you have, or tooling you provide, developers of different skill levels will have issues or edge cases to contend with. &lt;/p&gt;

&lt;p&gt;The only suggestions here are, if the product and budgets are small (or non-existent) then allow and enable the community of developers to support and help each other. Provide a place like Slack or Discord for developers to find other developers that may know what they don’t, and at the very least provide a searchable location for Q&amp;amp;A such as discourse or GitHub Wikis. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 
&lt;/h2&gt;

&lt;p&gt;Developer Experience is more than just these three things, as it can include the onboarding process or even the marketing material that developers consume. But they’re less focused on the problem of solving how developers use the product day-to-day. Another quick quote about what DX is and why it’s great: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Helping developers get sh❤️t done." &lt;br&gt;
&lt;a href="https://www.linkedin.com/in/whitep4nth3r/" rel="noopener noreferrer"&gt;Salma Alam-Naylor, @whitep4nth3r&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you or your company need help with Developer Experience, we at Digital Speed are more than happy to discuss your needs and see how we can help. And if we can’t help you directly, we can recommend organisations to work with that could help. &lt;/p&gt;

</description>
      <category>devex</category>
      <category>documentation</category>
      <category>tooling</category>
      <category>resources</category>
    </item>
    <item>
      <title>Dapr in the cloud with Catalyst public beta</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Fri, 27 Sep 2024 18:37:11 +0000</pubDate>
      <link>https://dev.to/ukmadlz/dapr-in-the-cloud-with-catalyst-public-beta-23j</link>
      <guid>https://dev.to/ukmadlz/dapr-in-the-cloud-with-catalyst-public-beta-23j</guid>
      <description>&lt;p&gt;I've been playing with this thing recently called &lt;a href="https://dapr.io/" rel="noopener noreferrer"&gt;Dapr&lt;/a&gt; (you can blame &lt;a class="mentioned-user" href="https://dev.to/marcduiker"&gt;@marcduiker&lt;/a&gt; for me finding out about the project).&lt;/p&gt;

&lt;p&gt;For those wondering what the heck Dapr is, you'd be right it does sound like it's dapper and means you need a fancy hat. &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%2F1lisufpof5l93erque1h.webp" 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%2F1lisufpof5l93erque1h.webp" alt="Dapper hat" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it's also a cool open-source project that is part of the &lt;a href="https://www.cncf.io/projects/dapr/" rel="noopener noreferrer"&gt;CNCF&lt;/a&gt;, you can check out the &lt;a href="https://github.com/dapr/dapr" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to go and investigate. What it's great at is acting as a common API and system for distributed event-driven applications.&lt;/p&gt;

&lt;p&gt;For what reason could I have been interested? Well, like most of us, I've been building a side project or two between contracts. And the one I'm most interested in finishing (feel free to ask me about it) is a distributed app across multiple vendors. I have a selection of the application in Railway doing responsive quick things and handling anything I'd need to worry about being highly available, and then the big static data crunching bits in containers on Digital Ocean. Each one handles its queues and pub/sub, and to communicate with each other they have "private" APIs to send and receive stuff.&lt;/p&gt;

&lt;p&gt;Wouldn't this be so much easier with something like Dapr acting as a common API to do service invocation of HA to long processes? Or pub/sub for anything that needs to be done immediately. Would you be happy to pass it back and forth for the user?&lt;/p&gt;

&lt;p&gt;Unfortunately for me, I haven't had the time to work out how to stand up and secure Dapr between the two vendors so it got left behind.&lt;/p&gt;

&lt;p&gt;But the title gives it away, Dapr is an open-source open-core technology. And as some of you may know, I love open-core tech.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.linkedin.com/posts/mikeelsmore_the-greatest-thing-about-open-core-projects-activity-7239210797355212800-cQ-t" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.licdn.com%2Faero-v1%2Fsc%2Fh%2Fc45fy346jw096z9pbphyyhdz7" height="800" class="m-0" width="1400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.linkedin.com/posts/mikeelsmore_the-greatest-thing-about-open-core-projects-activity-7239210797355212800-cQ-t" rel="noopener noreferrer" class="c-link"&gt;
            How to produce MVP on Open Core projects | Mike Elsmore posted on the topic | LinkedIn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            The greatest thing about Open Core projects is being able to produce the MVP on completely open tooling like Novu or configuring OpenTelemetry before having to look into vendors to use. If you have the time and capacity to do it this way I whole heartedly suggest you do it this way, saves the headache of realising something isn't the right fit and replacing it later.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.licdn.com%2Faero-v1%2Fsc%2Fh%2Fal2o9zrvru7aqj8e1x2rzsrca" width="64" height="64"&gt;
          linkedin.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;One of the organizations behind Dapr is called &lt;a href="https://www.diagrid.io/" rel="noopener noreferrer"&gt;Diagrid&lt;/a&gt;, and they have a managed service product for running Dapr workloads on Kubernetes called &lt;a href="https://www.diagrid.io/conductor" rel="noopener noreferrer"&gt;Conductor&lt;/a&gt;. But what I'm excited about is what they announced the other day, the public beta for &lt;a href="https://www.diagrid.io/catalyst" rel="noopener noreferrer"&gt;Catalyst&lt;/a&gt; (read about it here &lt;a href="https://www.diagrid.io/blog/announcing-catalyst-public-beta" rel="noopener noreferrer"&gt;https://www.diagrid.io/blog/announcing-catalyst-public-beta&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is exciting in my specific use case of multi-vendor IaaS &amp;amp; PaaS without having to manage it myself. That means I can probably replace my RabbitMQ instances in each vendor with Catalyst, as well as use its state management tooling as its key/value storage to handle data being shared between long-running processes without a shared database.&lt;/p&gt;

&lt;p&gt;You can see what Catalyst is currently capable of in this diagram. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.prod.website-files.com%2F66965adecd57031ed9ad184a%2F66f33fff540a9388d9646352_66f33b7f0a41219e8ae3666b_image%252520%2824%29.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%2Fcdn.prod.website-files.com%2F66965adecd57031ed9ad184a%2F66f33fff540a9388d9646352_66f33b7f0a41219e8ae3666b_image%252520%2824%29.png" alt="Catalyst Capability Diagram" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It even has a diagram explaining how I could use service invocation rather than "private" APIs to handle triggering services across vendors&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%2Fk36q5ix3m8u61at1a9dq.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%2Fk36q5ix3m8u61at1a9dq.png" alt="Service Invocation across Lambdas" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ignoring that the diagram is Lambdas rather than Railway to Digital Ocean, this would mean me removing &lt;em&gt;&lt;em&gt;a lot&lt;/em&gt;&lt;/em&gt; of retry boilerplate!&lt;/p&gt;

&lt;p&gt;If you're interested, I'd read about the capabilities of Dapr in general. And I'd jump on to the public beta to try it for yourself. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.diagrid.io/blog/announcing-catalyst-public-beta" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Catalyst Public Beta&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>opensource</category>
      <category>news</category>
      <category>api</category>
    </item>
    <item>
      <title>Some thoughts on DevTalks Bucharest 2024</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Thu, 27 Jun 2024 15:26:15 +0000</pubDate>
      <link>https://dev.to/ukmadlz/some-thoughts-on-devtalks-bucharest-2024-3bab</link>
      <guid>https://dev.to/ukmadlz/some-thoughts-on-devtalks-bucharest-2024-3bab</guid>
      <description>&lt;p&gt;I'm an exceedingly lucky person, over the years I've had the opportunity to attend and speak at (including &lt;em&gt;very&lt;/em&gt; last minute) multiple &lt;a href="https://www.devtalks.ro/" rel="noopener noreferrer"&gt;DevTalks&lt;/a&gt; events. And I was exceedingly happy to be invited to both moderate and speak at the 11th edition in Bucharest between May 29th &amp;amp; 30th.&lt;/p&gt;

&lt;p&gt;If you'd like a full run-down of all the speakers, talks, and activities they had I'd check out the website &lt;a href="https://www.devtalks.ro/" rel="noopener noreferrer"&gt;https://www.devtalks.ro/&lt;/a&gt; and the very active social media they run.&lt;/p&gt;

&lt;p&gt;I will admit that on day two, I spent nearly every moment on the product world stage being a moderator so all my thoughts on that are limited. But I can at least share about day one, and the expo booth. So here are my thoughts about the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expo
&lt;/h2&gt;

&lt;p&gt;Let's get the expo out of the way first, as that covers both days. In the event space, it had a &lt;em&gt;&lt;em&gt;HUGE&lt;/em&gt;&lt;/em&gt; expo between all the stages with amazing brands that I recognised and huge booths from local brands I didn't. You could chill and work in a dedicated workspace area that came with quiet/meeting room pods, and you could play with or watch so many robots including football and drawing… it was nuts.&lt;/p&gt;

&lt;p&gt;And the refreshments just kept coming! Amazing food trucks outside, coffee from plenty of Nespresso Professional machines with baristas, beers from Heineken (I can't stand the beer, but at least it was available), and all-you-can-drink soft drinks from Pepsi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day One
&lt;/h2&gt;

&lt;p&gt;Unfortunately, after a very stressful travel day into Bucharest from sunny Birmingham I slept in a little so I missed the opening speech and keynote (sorry dear reader). But I did manage to catch some awesome talks between bumping into old friends.&lt;/p&gt;

&lt;p&gt;The first talk I managed to stand at the back for was &lt;a href="https://dev.to/francescoxx"&gt;Francesco Ciulla&lt;/a&gt; from &lt;a href="https://daily.dev" rel="noopener noreferrer"&gt;Daily.dev&lt;/a&gt; on the Web Stage. The talk was titled Evolution of Web Development and I enjoyed hearing about how the culmination of standards was being used by Daily.dev to produce an amazing experience that's scalable across browsers.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1795729492985696427-934" src="https://platform.twitter.com/embed/Tweet.html?id=1795729492985696427"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1795729492985696427-934');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1795729492985696427&amp;amp;theme=dark"
  }



&lt;br&gt;
Next up I saw some of &lt;a href="https://x.com/vavillaiot" rel="noopener noreferrer"&gt;Vanessa Villa&lt;/a&gt; from &lt;a href="https://pangea.cloud/" rel="noopener noreferrer"&gt;Pangea&lt;/a&gt; on the DevLead Stage. This talk was something a little different for me as I'm not an IoT person beyond a little smart home tech, it was titled "IoT Evolution: Where is it now?". I'll admit she had my curiosity about the industrial applications of IoT right now, but I'm far removed from that, unfortunately.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1795730884118884493-961" src="https://platform.twitter.com/embed/Tweet.html?id=1795730884118884493"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1795730884118884493-961');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1795730884118884493&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Another talk from the DevLead stage had me watching my old friend &lt;a href="https://dev.to/lakatos88"&gt;Alex Lakatos&lt;/a&gt; from &lt;a href="https://interledger.org/" rel="noopener noreferrer"&gt;Interledger&lt;/a&gt;. "Building a Developer-first Culture" was the title, and this one was way too close to home for me. Having seen how Alex evolved this process, and how true it is. The biggest key takeaway is, with a small team, set the collaborative and open framework early so that everyone &lt;em&gt;wants&lt;/em&gt; to get involved with sharing how awesome the product they work on is!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1795797219745894735-224" src="https://platform.twitter.com/embed/Tweet.html?id=1795797219745894735"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1795797219745894735-224');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1795797219745894735&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;The final talk of day one that I got to see was &lt;a href="https://dev.to/elad2412"&gt;Elad Shechter&lt;/a&gt; from &lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;Appwrite&lt;/a&gt; on the Web Stage. His talk, titled "Update Your &amp;lt; Style &amp;gt;!", involved a lot of CSS, and seemed like black magic. But it was interesting to see how he implemented a grid as someone terrible at CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day Two
&lt;/h2&gt;

&lt;p&gt;Day two was a little different, I was moderating all day so I sat on one stage and watched it all. I was also a little stupid and forgot to take photos and post them to social media for future reference.&lt;/p&gt;

&lt;p&gt;I was moderating the Product World Stage that was powered by &lt;a href="https://metro.digital/" rel="noopener noreferrer"&gt;METRO.digital&lt;/a&gt; (the digital wing of the &lt;a href="https://www.metroag.de/en" rel="noopener noreferrer"&gt;METRO supermarkets&lt;/a&gt;). So it's only fitting that day two was opened up by representatives from METRO.digital.&lt;/p&gt;

&lt;p&gt;First up, we had &lt;a href="https://www.linkedin.com/in/adrian-postelnicu-83bba32/" rel="noopener noreferrer"&gt;Adrian Postelnicu&lt;/a&gt; who is the CPO for METRO.digital setting the scene for the day.&lt;/p&gt;

&lt;p&gt;Immediately from Adrian, we were introduced to &lt;a href="https://www.linkedin.com/in/aura-mihaela-virgolici/" rel="noopener noreferrer"&gt;Aura Virgolici&lt;/a&gt; (Engineering Manager at &lt;a href="https://metro.digital/" rel="noopener noreferrer"&gt;METRO.digital&lt;/a&gt;) &amp;amp; &lt;a href="https://www.linkedin.com/in/irina-poiana-19421121/" rel="noopener noreferrer"&gt;Irina Poiana&lt;/a&gt; (Domain Owner at &lt;a href="https://metro.digital/" rel="noopener noreferrer"&gt;METRO.digital&lt;/a&gt;). They gave a talk titled "Bridging the Gap: A Journey to High-Performing Teams" and it was about the pain points of a digital transformation program &lt;a href="https://metro.digital/" rel="noopener noreferrer"&gt;METRO.digital&lt;/a&gt;) went through.&lt;/p&gt;

&lt;p&gt;The second full talk of the day was from &lt;a href="https://www.linkedin.com/in/nesrinechanguel/" rel="noopener noreferrer"&gt;Nesrine Changuel&lt;/a&gt; giving a talk named "The Secret to Crafting Lovable Tech Products". Here she covered the idea of "delight" which was an interesting concept for features that don't necessarily bring direct ROI but do engage and make users love a product. You can even download the &lt;a href="https://www.nesrine-changuel.com/blogs_1/product-delight-map" rel="noopener noreferrer"&gt;Product Delight Map&lt;/a&gt; from the talk.&lt;/p&gt;

&lt;p&gt;Our first AI talk of the day was given by &lt;a href="https://www.linkedin.com/in/miroslavaleksandrov/" rel="noopener noreferrer"&gt;Miro Alexandrov&lt;/a&gt; from &lt;a href="https://www.ipsos.com/en-ca" rel="noopener noreferrer"&gt;Ipsos&lt;/a&gt;, his talk was "Leveraging Generative AI and Product Management Frameworks: How Ipsos is transforming Market Research". I did enjoy how he described Ipsos's use of AI to classify and enhance the existing research they have to improve and speed up projects.&lt;/p&gt;

&lt;p&gt;The last talk before the lunch break was &lt;a href="https://www.linkedin.com/in/luciangruiaro/" rel="noopener noreferrer"&gt;Lucian Gruia&lt;/a&gt; from &lt;a href="https://www.ciklum.com/" rel="noopener noreferrer"&gt;Ciklum&lt;/a&gt;. The talk "Coding Privacy - Aware Enterprise AI with RAG Architecture" was an overview of RAG and how it's implemented, which for someone like me who knows little about the implementation was a great starter.&lt;/p&gt;

&lt;p&gt;After lunch we had &lt;a href="https://www.linkedin.com/in/danmdinu/" rel="noopener noreferrer"&gt;Dan-Mihai Dinu&lt;/a&gt; from &lt;a href="https://bolt.eu/" rel="noopener noreferrer"&gt;Bolt&lt;/a&gt; who had the most dramatic and interesting start to his talk with costumes and all. The talk itself was called "Bolt Food: The Secret Recipe" and was an interesting run-through on the speed of development and iteration that Bolt has gone through with projects.&lt;/p&gt;

&lt;p&gt;The next talk from &lt;a href="https://www.linkedin.com/in/stefan-tudor-murariu/" rel="noopener noreferrer"&gt;Stefan Tudor Murariu&lt;/a&gt; was titled "The essential tools for early-stage product companies". His talk was about product development, giving some insight into getting over those first hurdles for finding product fit.&lt;/p&gt;

&lt;p&gt;Following on we had two speakers from &lt;a href="https://www.swissquote.com/" rel="noopener noreferrer"&gt;Swissquote&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/edfiaclou/" rel="noopener noreferrer"&gt;Edwige Fiaclou&lt;/a&gt; (Head Software Engineer Tech Talent &amp;amp; Methodologies) and  &lt;a href="https://www.linkedin.com/in/laetitia-aegerter-cuello-b107378/" rel="noopener noreferrer"&gt;Laetitia Aegerter-Cuello&lt;br&gt;
&lt;/a&gt; (Senior Agile Coach). The session "Agility à la carte: Product and Delivery a tasty combination for innovation" was a fun (it had Toblerone!) walk-through the Disciplined Agile process and it's effects on product delivery at &lt;a href="https://www.swissquote.com/" rel="noopener noreferrer"&gt;Swissquote&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our last AI talk of the day was from &lt;a href="https://www.linkedin.com/in/georgedita/" rel="noopener noreferrer"&gt;George Dita&lt;/a&gt; with a talk titled "Empower Your Product Management with AI: Enhance, Don’t Replace Your Unique Skills". George gave a breakdown of a toolchain they use for decision-making and process automation around product management.&lt;/p&gt;

&lt;p&gt;The final talk of the day (hurray it's over) was from yours truly, with the help of my friend Mike Dolha (you can find him on &lt;a href="https://www.instagram.com/mikeddol" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/mikeddol/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or his &lt;a href="https://tangents.transistor.fm/" rel="noopener noreferrer"&gt;podcast&lt;/a&gt;). My talk was "The ABC of DX", which was meant to be a practical guide to things organisations can do to improve internal and external Developer Experience so people love what they're building on or with. Mike then drove a quite hilarious Q&amp;amp;A session at the end. If you'd like a write-up of this talk and my thoughts etc please feel free to add a comment.&lt;/p&gt;
&lt;h2&gt;
  
  
  The conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.devtalks.ro/" rel="noopener noreferrer"&gt;DevTalks&lt;/a&gt; is an event that will always hold a special place in my heart, but Ow My God is it so busy! You cannot convey everything you could learn in a simple post. If you have the opportunity to attend, you should as Romania is a beautiful country with lovely cities (and food). And if you'd like to give a talk, the organisation behind it always runs a CFP. They also have a bunch of other events throughout the year &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.linkedin.com/embed/feed/update/urn:li:share:7212023757987581953" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4D22AQFX-M08PR6GRw%2Ffeedshare-shrink_2048_1536%2Ffeedshare-shrink_2048_1536%2F0%2F1719480456091%3Fe%3D2147483647%26v%3Dbeta%26t%3DL4Wvz1BxmXdgJdDpI5-VYxRVDm_S3LmpC9RwGK2ifS8" height="1080" class="m-0" width="1080"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.linkedin.com/embed/feed/update/urn:li:share:7212023757987581953" rel="noopener noreferrer" class="c-link"&gt;
            Exciting times are ahead this autumn, with three extraordinary events… | DevTalksRomania
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Exciting times are ahead this autumn, with three extraordinary events bringing the IT Community of Romania together. Make sure to mark your calendars! 🗓

➡ September 26th: DevTalks Cluj-Napoca at Cluj Innovation Park. Grab your early-bird tickets: https://bit.ly/3zl1xeu
➡ November 6th-7th: DevCon Bucharest at NORD Events Center by Globalworth. Stay tuned for more info! Meanwhile, check out what last year’s edition was like at www.dev-con.ro.
➡ November 21st: Empower - Our newest event, making its debut at NORD Events Center by Globalworth: www.empower-tech.ro/

🙌 We’re working hard to create the best experience for you. Follow us to stay updated and be the first to know more about our events!
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.licdn.com%2Faero-v1%2Fsc%2Fh%2Fal2o9zrvru7aqj8e1x2rzsrca" width="64" height="64"&gt;
          linkedin.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>conference</category>
      <category>learning</category>
    </item>
    <item>
      <title>You should build an SDK</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Thu, 22 Feb 2024 17:03:45 +0000</pubDate>
      <link>https://dev.to/infobipdev/you-should-build-an-sdk-1a3i</link>
      <guid>https://dev.to/infobipdev/you-should-build-an-sdk-1a3i</guid>
      <description>&lt;p&gt;Over the last little while in my career, I’ve been either involved in building or building SDKs, especially here at Infobip with the huge array of tools we have on offer. And this leads me down a rabbit hole of wondering why organisations don’t build them by default once they reach a certain size if they follow an API-first design approach.&lt;/p&gt;

&lt;p&gt;I’ve actually given talks on this subject a few times, if you want to skip reading, you can watch the last recording at &lt;iframe width="710" height="399" src="https://www.youtube.com/embed/FM30Z1uPq2E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an SDK?
&lt;/h2&gt;

&lt;p&gt;Let’s cover the basics: what is an SDK? The acronym itself means Software Development Kit, so it’s a toolkit for developing software. According to Wikipedia, the definition of an SDK is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A software development kit (SDK) is a collection of software development tools in one installable package. They facilitate the creation of applications by having a compiler, debugger and sometimes a software framework. They are normally specific to a hardware platform and operating system combination. To create applications with advanced functionalities such as advertisements, push notifications, etc; most application software developers use specific software development kits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Software_development_kit" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Software_development_kit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that I, and many like me who have treated SDKs as whatever the package manager installs, are a bit wrong (not totally, just a bit).  What I mean by this, is that Client Libraries, like those that wrap an HTTP API, are not the SDK but a part of an SDK. An SDK is a collection of tools; this can include your client libraries, but also other dev tools. It could include testing tooling, webhook definitions, or even mathematical models for doing a task. Those of us who build primarily for the web or for mobile applications forget that they can also be physical SDKs. Game developers working on next-gen hardware regularly get access to kits like these for the PS5&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%2Fawueeyyxicq4ghn9uz8h.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%2Fawueeyyxicq4ghn9uz8h.png" alt="PlayStation 5 Development Kit" width="620" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Infobip, we have a lot of SDKs, you can check them out here on &lt;a href="https://www.infobip.com/developers/sdks" rel="noopener noreferrer"&gt;our SDK page&lt;/a&gt;, but what I’ve been calling our NodeJS SDK is just a client library for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother building an SDK?
&lt;/h2&gt;

&lt;p&gt;Most people I know who are building SDKs are trying to enable a developer outside of their organisation, be it open source or proprietary, to consume the tools and products being built. I think this is wrong, and for a very simple reason, internal &amp;amp; external developers deserve the same tools.&lt;/p&gt;

&lt;p&gt;Yes, external developers need help and ease of onboarding or use to engage with technology. But so do internal developers, it’s not uncommon for organisations to be running different services, with different technologies, and differing knowledge or experience levels.&lt;/p&gt;

&lt;p&gt;Say the organisation you are in has some “legacy” systems running that no one actively works on but are used to grab information or tools that have been brought in over years and never updated. An internal client library providing a consistent set of functions to use them, as well as ETL tooling to make sure older standards are converted to the currently used ones, is a great SDK to build internally and manage. Or you could have REST, GraphQL, and WSDL systems that don’t have specification files or complete documentation on how to handle everything. A well-made SDK could solve that for teams.&lt;/p&gt;

&lt;p&gt;Essentially, for internal and external developers, an SDK can lower the learning curve, which leads to speeding up implementation and provides consistency across everything being built.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you build one?
&lt;/h2&gt;

&lt;p&gt;SDKs are meant to make life easier for the person using them, so the advice around developing them is rather straightforward.&lt;/p&gt;

&lt;p&gt;If you are developing a wrapper library or code to be used or to be imported and used in a code base, follow the instructions and best practices for the languages or frameworks it’s being developed for. For example, if you’re creating it for the Express framework in NodeJS then you’ll need to follow the middleware pattern that it requires.&lt;/p&gt;

&lt;p&gt;Where possible, use a namespace structure to allow people to only include the parts of the SDK they require if they do not need to use the whole thing. For example, &lt;a href="https://fastify.dev/" rel="noopener noreferrer"&gt;fastify&lt;/a&gt; is a web framework in NodeJS and only the core structure needed for routing and requests are within the main fastify module. For everything else, that is expected of a full nuts and bolts framework, they’re included as an ecosystem of &lt;code&gt;@fastify&lt;/code&gt; modules, like so &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%2F5c29h9n71adp77nbz90l.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%2F5c29h9n71adp77nbz90l.png" alt="The complete list of Fastify modules that make up the frameworks" width="800" height="976"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are developing a client library, follow a standard. Don’t hop about code styles, etc., as it’ll defeat the point of being easy to consume. And whenever you have to break the standard, make it known. Most languages and development environments allow you to easily see things like &lt;a href="https://en.wikipedia.org/wiki/Docblock" rel="noopener noreferrer"&gt;DocBlocks&lt;/a&gt; to explain why and also how to consume this edge case.&lt;/p&gt;

&lt;p&gt;Let’s move on to something controversial among my peers. Personally, I think it is totally OK to use generators to produce client libraries. If you’re using standards like the OpenAPI Specification, you can use a plethora of generators to produce the first version of your client library codebase. And most importantly, where it isn’t fit for your purposes, you can modify the template, or completely extend the generator to give you the library you want. I definitely endorse this practice, as it’s something we do here at Infobip.&lt;/p&gt;

&lt;p&gt;And now for some best practice tips, just to make everyone’s life a little easier to maintain them. Use version control on the code base that’s in-line with the language it’s been developed in. Additionally, where possible, use dependency management tooling and static analysis tools to make the maintenance and security risks manageable.&lt;/p&gt;

</description>
      <category>sdk</category>
      <category>devex</category>
      <category>api</category>
      <category>development</category>
    </item>
    <item>
      <title>Automating your CloudQuery Policies with CircleCI</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Tue, 01 Mar 2022 10:48:20 +0000</pubDate>
      <link>https://dev.to/ukmadlz/automating-your-cloudquery-policies-with-circleci-355c</link>
      <guid>https://dev.to/ukmadlz/automating-your-cloudquery-policies-with-circleci-355c</guid>
      <description>&lt;p&gt;This blog is going to cover running CloudQuery in CircleCI as part of your continuous integration or continuous delivery pipeline.&lt;/p&gt;

&lt;p&gt;We all know how awesome a tool CloudQuery is, with a bit of configuration you very quickly get access to the &lt;code&gt;cloudquery fetch&lt;/code&gt; command to get information on your cloud assets, and then you can start running &lt;code&gt;cloudquery policy run &amp;lt;policy&amp;gt;&lt;/code&gt; against that to get amazing guidance on your cloud infrastructure. But what about automating the process, or making it accessible to the rest of your team?&lt;/p&gt;

&lt;p&gt;You could use our &lt;a href="https://docs.cloudquery.io/docs/deployment/helm-chart" rel="noopener noreferrer"&gt;helm chart&lt;/a&gt; to deploy a dedicated and persistent version of CloudQuery, or if you want to experiment with you could add this to your CI/CD processes. I personally really like using CircleCI and have been an active user since about 2017, so for this example, we’ll be creating a CircleCI workflow (and &lt;a href="https://github.com/ukmadlz/cloudquery-circleci-template" rel="noopener noreferrer"&gt;template&lt;/a&gt;) to achieve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is CI/CD?
&lt;/h2&gt;

&lt;p&gt;CI, or continuous integration, in its simplest form is the means of continually adding and integrating code into a shared body of code. In Git terms, this is the creation of changes in a branch, followed by the testing, and then merging into the main branch.&lt;/p&gt;

&lt;p&gt;CD, or continuous delivery, is the methodology by which you can deploy your codebase at any time. This means making sure that the codebase is tested, and has an automated release process, rather than manually grabbing the code and loading it to a location.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to add CloudQuery to CircleCI?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Make sure you’ve followed one of our &lt;a href="https://docs.cloudquery.io/docs/getting-started/getting-started-with-aws" rel="noopener noreferrer"&gt;Getting Started Guides&lt;/a&gt; to make sure you have a valid and functional &lt;code&gt;config.hcl&lt;/code&gt; for CloudQuery to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started with CircleCI
&lt;/h3&gt;

&lt;p&gt;If you’ve never used CircleCI before, rather than walk through all the steps here I’d recommend reading through the &lt;a href="https://circleci.com/docs/2.0/getting-started/" rel="noopener noreferrer"&gt;Your First Green Build&lt;/a&gt; guide in the CircleCI documentation. This guide will take you through the creation of your &lt;code&gt;.circleci/config.yml&lt;/code&gt; and what the core parts are.&lt;/p&gt;

&lt;p&gt;Once you’ve got that started, here is how to add CloudQuery to your CircleCI flow.&lt;/p&gt;

&lt;p&gt;The first step is to add to the list of &lt;code&gt;jobs&lt;/code&gt;, each of these jobs is a distinct task and process to complete. Here is the job to fetch data to a Postgres instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Define a job to be invoked later in a workflow.&lt;/span&gt;
&lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#jobs&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.&lt;/span&gt;
    &lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cimg/base:stable&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CQ_DSN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://postgres:pass@localhost/postgres?sslmode=disable&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:11&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="c1"&gt;# Add steps to the job&lt;/span&gt;
    &lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#steps&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CloudQuery"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -L https://github.com/cloudquery/cloudquery/releases/latest/download/cloudquery_linux_x86_64 -o cloudquery &amp;amp;&amp;amp; chmod a+x cloudquery&lt;/span&gt;
      &lt;span class="c1"&gt;#  Wait for Postgres to be ready before proceeding&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Waiting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Postgres&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ready"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerize -wait tcp://localhost:5432 -timeout 1m&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Resources"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cloudquery fetch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you have a new job, you need to set the container that will execute the task and any supporting containers. In this case, we have an image of &lt;code&gt;cimg/base:stable&lt;/code&gt; which is a basic Linux image to execute tasks as our primary container with a supporting &lt;code&gt;postgres:11&lt;/code&gt; image.&lt;/p&gt;

&lt;p&gt;We also have to provide the &lt;code&gt;CQ_DSN&lt;/code&gt; variable, following this &lt;a href="https://docs.cloudquery.io/docs/configuration/overview#environment-variable-substitution" rel="noopener noreferrer"&gt;ENV substitution guide&lt;/a&gt;, to point to the Postgres instance within the CircleCI job.&lt;/p&gt;

&lt;p&gt;Next, we have the &lt;code&gt;steps&lt;/code&gt;, these are the commands executed on the primary container to add, configure, and perform tasks. The first task is usually &lt;code&gt;checkout&lt;/code&gt;, this command will clone your codebase from your git repository to run against.&lt;/p&gt;

&lt;p&gt;The following step is &lt;code&gt;Install CloudQuery&lt;/code&gt;, this follows the &lt;a href="https://docs.cloudquery.io/docs/getting-started/getting-started-with-aws#download-and-install" rel="noopener noreferrer"&gt;Download and Install&lt;/a&gt; process of adding CloudQuery to a Linux instance and configuring it to be executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CloudQuery"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -L https://github.com/cloudquery/cloudquery/releases/latest/download/cloudquery_linux_x86_64 -o cloudquery &amp;amp;&amp;amp; chmod a+x cloudquery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to the way CircleCI parallelises its container, it does take a moment for the Postgres image to be ready so we do have a wait step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Waiting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Postgres&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ready"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerize -wait tcp://localhost:5432 -timeout 1m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step is using &lt;code&gt;dockerize&lt;/code&gt; to make sure the Postgres image is available to use before executing any commands against it.&lt;/p&gt;

&lt;p&gt;And finally, we run CloudQuery itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Resources"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cloudquery fetch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;cloudquery fetch&lt;/code&gt; the container either uses the &lt;code&gt;.cq&lt;/code&gt; information if committed to the repository or downloads a fresh version of the providers configured and grabs the data from your provider to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring CircleCI
&lt;/h3&gt;

&lt;p&gt;Now for this to work, you’ll need to add the access keys etc in CircleCI. It’s usually best to do this on a per-project basis. Within CircleCI go to projects, then select the project in question:&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%2F43d4odhxn5vi6wiuh5b4.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%2F43d4odhxn5vi6wiuh5b4.png" alt="Select a Project within CircleCI" width="625" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here navigate to &lt;code&gt;Project Settings&lt;/code&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkolu07j2zrinog9x2vmc.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%2Fkolu07j2zrinog9x2vmc.png" alt="Navigate to Project Settings" width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here select &lt;code&gt;Environment Variables&lt;/code&gt;, and from here you can add your ENV variables as a key-value pair:&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%2F3kylqjs1xpt5cis9v6uw.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%2F3kylqjs1xpt5cis9v6uw.png" alt="Add relevant ENV variables to the CircleCI project" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make this work, you now need to add a &lt;code&gt;workflows&lt;/code&gt; reference. This is how you chain jobs together to accomplish tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Invoke jobs via workflows&lt;/span&gt;
&lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#workflows&lt;/span&gt;
&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fetch-workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow &lt;code&gt;fetch-workflow&lt;/code&gt; will now execute on every branch pushed to your git repository. But once the &lt;code&gt;fetch&lt;/code&gt; is complete the data is lost as the Postgres image is destroyed after use, which isn’t much use for sharing the information gathered from the provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a policy check
&lt;/h2&gt;

&lt;p&gt;Once you have added a policy from the &lt;a href="https://hub.cloudquery.io/policies" rel="noopener noreferrer"&gt;CloudQuery Hub&lt;/a&gt; or written your &lt;a href="https://docs.cloudquery.io/docs/tutorials/policies/policies-overview" rel="noopener noreferrer"&gt;Custom Policy&lt;/a&gt;, you’ll want to run this nightly or after deployment and to this we can extend the example above quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;…&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.&lt;/span&gt;
    &lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cimg/base:stable&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CQ_DSN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://postgres:pass@localhost/postgres?sslmode=disable&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:11&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="c1"&gt;# Add steps to the job&lt;/span&gt;
    &lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#steps&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CloudQuery"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -L https://github.com/cloudquery/cloudquery/releases/latest/download/cloudquery_linux_x86_64 -o cloudquery &amp;amp;&amp;amp; chmod a+x cloudquery&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Waiting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Postgres&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ready"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerize -wait tcp://localhost:5432 -timeout 1m&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Resources"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cloudquery fetch&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Policy"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cloudquery policy run aws --output-dir ./&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;store_artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./aws.json&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-policy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our new &lt;code&gt;policy&lt;/code&gt; job is the same as the previous &lt;code&gt;build&lt;/code&gt; job except it has two additional steps. The first of which is &lt;code&gt;Check AWS Policy&lt;/code&gt; step, this is running the entire &lt;a href="https://hub.cloudquery.io/policies/cloudquery/aws/latest" rel="noopener noreferrer"&gt;AWS Policy&lt;/a&gt; set against the gathered data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Policy"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cloudquery policy run aws --output-dir ./&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because of the use of the &lt;code&gt;--output-dir&lt;/code&gt; flag, it’s creating an &lt;code&gt;aws.json&lt;/code&gt; for the results of the policy checks.&lt;/p&gt;

&lt;p&gt;The final step is storing the &lt;code&gt;aws.json&lt;/code&gt; as an artifact so that after the execution of the check, and the subsequent destruction of the image you can still review the results of the policy check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;store_artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./aws.json&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-policy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Early we mentioned running this as a nightly task, CircleCI workflows have a &lt;code&gt;trigger&lt;/code&gt; for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Invoke jobs via workflows&lt;/span&gt;
&lt;span class="c1"&gt;# See: https://circleci.com/docs/2.0/configuration-reference/#workflows&lt;/span&gt;
&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fetch-workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;…&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;policy-check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
          &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;policy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you add the &lt;code&gt;schedule&lt;/code&gt; to the &lt;code&gt;triggers&lt;/code&gt; you can follow the &lt;code&gt;crontab&lt;/code&gt; standard to decide when it’s executed, and any other &lt;code&gt;filters&lt;/code&gt; you may want to limit this execution by. This is a legacy method of automating within CircleCI, for a more up-to-date approach that requires a little more configuration I’d use &lt;a href="https://circleci.com/blog/using-scheduled-pipelines/" rel="noopener noreferrer"&gt;Scheduled Pipelines&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This blog should have covered all the aspects of using CloudQuery with CircleCI to automate your policy checks, and with that make it shareable with your teammates. If you’d like to discuss other ways to use CloudQuery in CI you can join our &lt;a href="https://cloudquery.io/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; and talk to us about your needs. And if we’ve missed anything we’d happily follow up this blog with more details, or even other CI/CD providers.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>circleci</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>Running AWS Foundational Security Best Practices with CloudQuery Policies</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Mon, 28 Feb 2022 09:32:31 +0000</pubDate>
      <link>https://dev.to/ukmadlz/running-aws-foundational-security-best-practices-with-cloudquery-policies-1li9</link>
      <guid>https://dev.to/ukmadlz/running-aws-foundational-security-best-practices-with-cloudquery-policies-1li9</guid>
      <description>&lt;p&gt;Back in mid-2020 AWS Security Hub released a new security standard called AWS Foundational Security Best Practices. This new standard sets security controls to detect when an AWS account or deployed resources don’t match up to the best practices set out by the AWS security experts. The complete standard can be found in the &lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp.html" rel="noopener noreferrer"&gt;AWS Security Hub documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As with any security guidelines, factors such as AWS environments, requirements, and capacity of your security team, will impact how you implement those guidelines.&lt;/p&gt;

&lt;p&gt;The new AWS Foundational Security Best Practices CloudQuery policy gives you a powerful way to automate, customize, codify, and run your cloud security &amp;amp; compliance continuously with HCL and SQL.&lt;/p&gt;

&lt;p&gt;The CloudQuery AWS Foundational Security Policy covers 200+ checks - you can review them on &lt;a href="https://github.com/cloudquery-policies/aws/tree/main/foundational_security" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or review them in the &lt;a href="https://hub.cloudquery.io/policies/cloudquery/aws/latest/policies/foundational_security" rel="noopener noreferrer"&gt;CloudQuery Hub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Please follow the &lt;a href="https://docs.cloudquery.io/docs/getting-started/getting-started-with-aws" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt; documentation on how to install cloudquery, and &lt;code&gt;fetch&lt;/code&gt; your AWS configuration into a PostgreSQL database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running
&lt;/h2&gt;

&lt;p&gt;After fetching your AWS configuration into a PostgreSQL database, you can use SQL to check your cloud deployment for compliance!&lt;/p&gt;

&lt;p&gt;For example, you can check for certificates that are going to expire soon and need to be renewed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cloudquery&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;policies&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;acm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;certificates_should_be_renewed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;arn&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;aws_acm_certificates&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;not_after&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'UTC'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'30'&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use the &lt;code&gt;cloudquery&lt;/code&gt; command to run the entire AWS Foundational Security Best Practices policy pack. The policy is split into sections as sub-policies, so you can run either the entire policy, a sub-policy, or even one specific check.&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;# execute the AWS foundational-security-best-practices policy pack&lt;/span&gt;

cloudquery policy run aws//foundational_security

&lt;span class="c"&gt;# execute the ACM section in AWS Foundational Security policy&lt;/span&gt;

cloudquery policy run aws//foundational_security/acm

&lt;span class="c"&gt;# execute the S3 related section in AWS Foundational Security policy&lt;/span&gt;

cloudquery policy run aws//foundational_security/s3

&lt;span class="c"&gt;# describe all available policies and sub-policies available for AWS on cloudquery&lt;/span&gt;

cloudquery policy describe aws

&lt;span class="c"&gt;# execute the entire AWS policy pack, including other benchmarks.&lt;/span&gt;

cloudquery policy run aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also output the results into a json and pass them to downstream processing for automated monitoring and alerting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudquery policy run aws//foundational_security &lt;span class="nt"&gt;--output-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build your own and share!
&lt;/h2&gt;

&lt;p&gt;Do you have a policy that you want to codify, or that you’ve been running with python or bash scripts? You are welcome to try codifying it with CloudQuery Policies (See our &lt;a href="https://github.com/cloudquery-policies/aws" rel="noopener noreferrer"&gt;github&lt;/a&gt; and &lt;a href="https://docs.cloudquery.io/docs/cli/policy/language" rel="noopener noreferrer"&gt;docs&lt;/a&gt; for how to develop one). Feel free to visit our &lt;a href="https://cloudquery.io/discord" rel="noopener noreferrer"&gt;discord&lt;/a&gt; or &lt;a href="https://github.com/cloudquery" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to get help - we’ll also be happy to share your policy on &lt;a href="https://hub.cloudquery.io/" rel="noopener noreferrer"&gt;CloudQuery Hub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>infrastructure</category>
      <category>devops</category>
    </item>
    <item>
      <title>What are policies and how do you use them with CloudQuery?</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Mon, 21 Feb 2022 13:00:10 +0000</pubDate>
      <link>https://dev.to/ukmadlz/what-are-policies-and-how-do-you-use-them-with-cloudquery-2o1i</link>
      <guid>https://dev.to/ukmadlz/what-are-policies-and-how-do-you-use-them-with-cloudquery-2o1i</guid>
      <description>&lt;p&gt;Policy is a very broad term and in this blog we’ll be explaining what policies are, how we implement them at CloudQuery, and how you can use this in the security &amp;amp; compliance of your applications.&lt;/p&gt;

&lt;p&gt;We have a good definition of what a policy is within our &lt;a href="https://docs.cloudquery.io/docs/glossary#policy" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, and it reads as so&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Policy compliance is a broad term and can refer to any kind of policy, from internal standards to regulatory requirements. A CloudQuery Policy is a codified form of this that is written with HCL as the logic layer and SQL as the query layer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simplified this means a policy is a set of rules to be followed, for example, a password policy requiring you to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be alphanumeric&lt;/li&gt;
&lt;li&gt;Have a special character&lt;/li&gt;
&lt;li&gt;Have a number&lt;/li&gt;
&lt;li&gt;Changed every 4 weeks&lt;/li&gt;
&lt;li&gt;No repeat password for 13 weeks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a really basic policy that is standard for a lot of applications or organisations.&lt;/p&gt;

&lt;p&gt;In the wild various vendors have policies, and various standards bodies have varying policies that your applications may have to validate against to be classified as safe for certain usage. Even huge providers like AWS have made themselves compliant with &lt;a href="https://aws.amazon.com/compliance/iso-27001-faqs/" rel="noopener noreferrer"&gt;ISO 27001&lt;/a&gt; and a whole &lt;a href="https://aws.amazon.com/compliance/" rel="noopener noreferrer"&gt;gamut of others&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the common policies?
&lt;/h2&gt;

&lt;p&gt;A few of the most common policies you can come across are things like HIPAA (US healthcare) which has a full summary of &lt;a href="https://www.hhs.gov/hipaa/for-professionals/security/laws-regulations/index.html" rel="noopener noreferrer"&gt;security rules&lt;/a&gt; to follow. But for most of us in the software industry, we are going to come into contact with these two, CIS (&lt;a href="https://www.cisecurity.org/cis-benchmarks/" rel="noopener noreferrer"&gt;Center for Internet Security&lt;/a&gt;) and PCI DSS (&lt;a href="https://www.pcisecuritystandards.org/" rel="noopener noreferrer"&gt;Payment Card Industry Data Security Standard&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  How does CloudQuery work with policies?
&lt;/h2&gt;

&lt;p&gt;CloudQuery is an open-source cloud asset inventory powered by SQL, so a policy to the tool is simply an HCL configuration file that references SQL queries. How this works is by CloudQuery ingesting the data from your cloud provider, such as AWS or Azure, and then the policy executes the SQL statements against that data as if it were a test suite. Our co-founder &lt;a href="https://twitter.com/yevgenypats" rel="noopener noreferrer"&gt;Yevgeny&lt;/a&gt; recently wrote an amazing piece on running the &lt;a href="https://www.cloudquery.io/blog/running-aws-pci-dss-with-cloudquery-policies" rel="noopener noreferrer"&gt;PCI DSS policy against AWS&lt;/a&gt;. If you are curious about the multitude of policies currently available you can check them out at &lt;a href="https://hub.cloudquery.io/" rel="noopener noreferrer"&gt;https://hub.cloudquery.io/&lt;/a&gt; which is our centrally available and searchable source for publicly available policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use policies?
&lt;/h2&gt;

&lt;p&gt;As an example, we shall use AWS to check for compliance. Initially, you will need to install CloudQuery, to do this follow the instructions available in the &lt;a href="https://docs.cloudquery.io/docs/getting-started/getting-started-with-aws/" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt; section of our documentation for your given operating system.&lt;/p&gt;

&lt;p&gt;Now that you have CloudQuery installed you will need to create the configuration, with the provider you wish to connect it to which in our case is AWS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudquery init aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the &lt;code&gt;config.hcl&lt;/code&gt; relative to where you ran this command, for a breakdown of how this is structured it’s available here &lt;a href="https://docs.cloudquery.io/docs/configuration/overview" rel="noopener noreferrer"&gt;https://docs.cloudquery.io/docs/configuration/overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don’t already have a Postgres instance running for the data (you will need to change the &lt;code&gt;connection&lt;/code&gt; block of the &lt;code&gt;config.hcl&lt;/code&gt; if you do) then run the 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;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this is started you can run the following command to start the process of getting the data from AWS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudquery fetch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you have the asset configuration available you can run policies against them. If you’d like to download them in advance you can execute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudquery policy download aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This downloads the policy configuration and SQL to use later within the &lt;code&gt;.cq&lt;/code&gt; directory, it also returns a block that you can add to your &lt;code&gt;config.hcl&lt;/code&gt; that may look similar to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would like to skip the download step, you could execute the following commands to review and execute the policies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# execute the whole policy pack (cis, pci dss, etc)&lt;/span&gt;
&lt;span class="nx"&gt;cloudquery&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;

&lt;span class="c1"&gt;# execute specific policy pack&lt;/span&gt;
&lt;span class="nx"&gt;cloudquery&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="c1"&gt;//pci_dss_v3.2.1&lt;/span&gt;

&lt;span class="c1"&gt;# execute specific section in PCI DSS&lt;/span&gt;
&lt;span class="nx"&gt;cloudquery&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="c1"&gt;//pci_dss_v3.2.1/autoscaling/1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s that simple and you will get a PASS/FAIL return list from all these existing policy statements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making a custom policy?
&lt;/h2&gt;

&lt;p&gt;If you want to make a policy to automate some of your checks rather than writing the SQL repeatedly or storing them as a stored procedure, then it’s relatively easy.&lt;/p&gt;

&lt;p&gt;For a full tutorial on creating policies, please follow the &lt;a href="https://docs.cloudquery.io/docs/tutorials/policies/policies-overview" rel="noopener noreferrer"&gt;tutorial in our documentation&lt;/a&gt;, but for a quick guide keep reading.&lt;/p&gt;

&lt;p&gt;The first step to a custom policy is to allow &lt;code&gt;cloudquery&lt;/code&gt; to find it, the easiest way to do that is to add a policy block to the &lt;code&gt;config.hcl&lt;/code&gt; and this can look like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"my-custom-policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./path/to/policy/directory"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside your new policy you will need a &lt;code&gt;policy.hcl&lt;/code&gt; to define what your policy is, a simple example can be just like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"my-custom-policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is a test policy"&lt;/span&gt;
  &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;README&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"theprovideritusesname"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After defining what the policy is, and its prerequisites for data such as the &lt;code&gt;provider&lt;/code&gt; or documentation, you need to define the actual policy definitions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"my-custom-policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is a custom test policy"&lt;/span&gt;
  &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;README&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"theprovideritusesname"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"nested-policy-peter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A policy about Peter"&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/policy.hcl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"nested-policy-james"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A policy about James"&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"james/policy.hcl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for the policy itself, or the sub-policy in this case, we have out &lt;code&gt;my-custom-policy&lt;/code&gt; and we are now creating the “peter” sub-policy. This should look like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"peter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Policy specifically for Peter"&lt;/span&gt;
  &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/README.md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"theprovideritusesname"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt; 1.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"meal"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/meal.hcl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that this sub-policy defines the prerequisites, like the provider that supplies its data etc, as the initial policy did before. And as before the &lt;code&gt;policy&lt;/code&gt; block here references another &lt;code&gt;hcl&lt;/code&gt;, but this one contains the query to execute and looks like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="err"&gt;​​&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="s2"&gt;"meal"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Section Meal: Information regarding Peter’s meals"&lt;/span&gt;
  &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/docs/meals.md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="s2"&gt;"breakfast"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"What does Peter have to breakfast"&lt;/span&gt;
    &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/docs/breakfast.md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"queries/meals/breakfast_eaten.sql"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="s2"&gt;"lunch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"What does Peter have to lunch"&lt;/span&gt;
    &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/docs/lunch.md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"queries/meals/lunch_eaten.sql"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="s2"&gt;"dinner"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"What does Peter have to dinner"&lt;/span&gt;
    &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"peter/docs/dinner.md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"queries/meals/dinner_eaten.sql"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can see the policy specifically for Peters meals, and we aren’t going to another nested level, instead we have the &lt;code&gt;check&lt;/code&gt; block. This block contains the &lt;code&gt;title&lt;/code&gt; of the check, any &lt;code&gt;doc&lt;/code&gt; concerning it, and a reference to the SQL &lt;code&gt;query&lt;/code&gt; that’s contained in a &lt;code&gt;.sql&lt;/code&gt; file (NOTE: you can just write the SQL into the query block-like `query = “SELECT 1;”, but we don’t advise that for query reuse).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;tip&lt;br&gt;
All the&lt;/code&gt;file&lt;code&gt;includes are from the root directory that you set the first&lt;/code&gt;policy.hcl` in, leaving you with a directory structure a little like so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;policy.hcl&lt;/li&gt;
&lt;li&gt;peter

&lt;ul&gt;
&lt;li&gt;Docs

&lt;ul&gt;
&lt;li&gt;Meals.md&lt;/li&gt;
&lt;li&gt;Breakfast.md&lt;/li&gt;
&lt;li&gt;Lunch.md&lt;/li&gt;
&lt;li&gt;dinner.md&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;policy.hcl&lt;/li&gt;

&lt;li&gt;meals.hcl&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;queries

&lt;ul&gt;
&lt;li&gt;meals

&lt;ul&gt;
&lt;li&gt;breakfast_eaten.sql&lt;/li&gt;
&lt;li&gt;lunch_eaten.sql&lt;/li&gt;
&lt;li&gt;dinner_eaten.sql
`&lt;code&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And with this, you can create policies to automate the checks, or your internal organisation policies, against your assets in the cloud. If you’d like more documentation on policies, you can check out the &lt;a href="https://docs.cloudquery.io/docs/cli/policy/overview" rel="noopener noreferrer"&gt;policy documentation&lt;/a&gt; or the &lt;a href="https://hub.cloudquery.io/policies" rel="noopener noreferrer"&gt;hubs policy page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are looking for an open-source cloud asset inventory powered by SQL, check out our &lt;a href="https://github.com/cloudquery/cloudquery" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, Feel free to join our &lt;a href="https://cloudquery.io/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; if you run into any bugs/issues, or just want to chat.&lt;/p&gt;

</description>
      <category>security</category>
      <category>infrastructure</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AWS SSO Tutorial with Google Workspace (Gsuite) as an IdP Step-by-Step</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Fri, 26 Nov 2021 14:44:01 +0000</pubDate>
      <link>https://dev.to/ukmadlz/aws-sso-tutorial-with-google-workspace-gsuite-as-an-idp-step-by-step-1866</link>
      <guid>https://dev.to/ukmadlz/aws-sso-tutorial-with-google-workspace-gsuite-as-an-idp-step-by-step-1866</guid>
      <description>&lt;p&gt;AWS SSO and AWS Organization were released around 2017 and are probably the best way to manage AWS access at scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"AWS Single Sign-On (SSO) is a cloud SSO service that makes it easy to centrally manage SSO access to multiple AWS accounts and business applications. It enables users to sign in to an AWS IAM user with their existing corporate credentials and access all of their assigned accounts and applications from one place."&lt;br&gt;
&lt;em&gt;Quote From AWS SSO page&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a huge security and operational win, some highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to rotate another new password in AWS IAM&lt;/li&gt;
&lt;li&gt;2FA is already managed at your IdP (Google Workspace (Gsuite)/Okta/AzureAD) level&lt;/li&gt;
&lt;li&gt;When a user is leaving an organization he is automatically removed access from the organization&lt;/li&gt;
&lt;li&gt;Easily automate the provisioning of AWS access when a user joins an organisation or department&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article we, will go through a step-by-step guide to set-up AWS SSO with Google Workspace (previously Gsuite) as an IdP. If you are using Google Workspace and use it as your central directory, this is the guide for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;p&gt;You should have the &lt;a href="https://aws.amazon.com/organizations/" rel="noopener noreferrer"&gt;AWS Organization&lt;/a&gt; (If you are not using it, This service combined with AWS SSO is a real game changer) set-up. &lt;/p&gt;

&lt;p&gt;You need to signup from the main account (also called &lt;strong&gt;"management account"&lt;/strong&gt; ) and with enough permissions (usually Administrator permissions).&lt;/p&gt;

&lt;p&gt;You will also need to make sure that you have access to the Google Workspace Admin and the relevant permissions to manage it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up AWS
&lt;/h3&gt;

&lt;p&gt;Now that you have all the relevant permissions, everything is ready to configure for AWS SSO. Here is the step by step to set it all up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;From within the &lt;a href="http://console.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt; search so &lt;code&gt;single sign on&lt;/code&gt; and go to the &lt;a href="https://console.aws.amazon.com/singlesignon" rel="noopener noreferrer"&gt;AWS Single Sign-on&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage26.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage26.png" alt="Find AWS Single Sign-on" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once on the service page, click the &lt;code&gt;Enable AWS SSO&lt;/code&gt; button to start the service. This will take a few moments to complete.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage29.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage29.png" alt="Enable AWS Single Sign-on" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that SSO is enabled, we need to change from the AWS directory to using an external provider. Select &lt;code&gt;Choose your identity source&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage1.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage1.png" alt="Choose your identity source" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Within the Settings page, select &lt;code&gt;Change&lt;/code&gt; under the &lt;code&gt;Identity source&lt;/code&gt; 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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage31.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage31.png" alt="Identity source" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we can change from the AWS SSO directory to an Active Directory (not what we need), or an &lt;code&gt;External identity provider&lt;/code&gt; which is what we need to configure Google Workspace as the 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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage28.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage28.png" alt="External identity provider" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After you have selected &lt;code&gt;External identity provider&lt;/code&gt;, scroll down to &lt;code&gt;Service provider metadata&lt;/code&gt; and click &lt;code&gt;Show individual metadata values&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage15.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage15.png" alt="Show individual metadata values" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You should now be presented with three fields that you can use to configure the next step on Google Workspace in the Google Admin console.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage16.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage16.png" alt="Configuration for Google Workspace" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't close this screen, you will need it shortly after you have done the next section.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Google Workspace SAML setup
&lt;/h3&gt;

&lt;p&gt;With the SSO Urls for our AWS organization, we can go to our Google Workspace Admin console and configure it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When inside the &lt;a href="https://admin.google.com/" rel="noopener noreferrer"&gt;Google Workspace Admin console&lt;/a&gt;, go to the &lt;code&gt;Web and mobile apps&lt;/code&gt; settings. You can find this in the left-hand navigation menu under &lt;code&gt;Apps&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage12.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage12.png" alt="Web and mobile apps" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then select &lt;code&gt;Add App&lt;/code&gt; from the top navigation, then &lt;code&gt;Add custom SAML app&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage2.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage2.png" alt="Add custom SAML app" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add an &lt;code&gt;App name&lt;/code&gt; for the integration, I'm using &lt;code&gt;AWS SSO&lt;/code&gt; to make it easier to find later.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage18.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage18.png" alt="Set the App name" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We suggest you download the Google IdP metadata ready to put it back into AWS, this is under &lt;code&gt;Option 1: Download IdP metadata&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage5.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage5.png" alt="Option 1: Download IdP metadata" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now to add the AWS SSO Urls from earlier to configure Google Workspace to point to the correct location. The mapping of data is:&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage22.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage22.png" alt="Add the AWS SSO Urls" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- For ACS URL, enter the AWS SSO ACS URL.
- For Entity ID, enter the AWS SSO Issue URL.
- For Start URL, leave the field blank.
- For Name ID format, choose EMAIL.
- For Name ID, choose Basic Information &amp;gt; Primary email.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We don't need to apply anything to the &lt;code&gt;Attribute mapping&lt;/code&gt; settings, so you can just click &lt;code&gt;FINISH&lt;/code&gt; to move forward.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage24.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage24.png" alt="Click FINISH" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once that's saved, it is time to enable it for everyone. In the &lt;code&gt;User access&lt;/code&gt; section, open the settings by selecting the karat in the top right corner.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage30.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage30.png" alt="User access settings" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that you're in the Service status screen, select &lt;code&gt;ON for everyone&lt;/code&gt; and &lt;code&gt;SAVE&lt;/code&gt;. This will enable the service and allow you to manage who can have access to AWS, but to configure what they can access you need to do that in AWS SSO as Google Workspace is unaware of all the possible options.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage11.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage11.png" alt="ON for everyone" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Adding Google Workspace configuration to AWS SSO
&lt;/h3&gt;

&lt;p&gt;Now that the AWS SSO service is enabled, and the Google Workspace SAML app exists, it's time to make them talk to each other.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go back to the &lt;code&gt;Change identity source&lt;/code&gt; screen in AWS SSO. Scroll to the bottom and add the &lt;code&gt;GoogleIDPMetadata.xml&lt;/code&gt; file you downloaded a few moments ago, then click &lt;code&gt;Next: Review&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage17.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage17.png" alt="Add GoogleIDPMetadata.xml" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To confirm this new identity source, you will need to type &lt;code&gt;ACCEPT&lt;/code&gt; into the field under the warnings and then select &lt;code&gt;Change identity source&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage20.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage20.png" alt="Confirm new identity source" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And now you are done with configuring the SSO and SAML connection between AWS SSO and Google Workspace. However, you aren't quite done as you need to configure the user provisioning at this point.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage23.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage23.png" alt="Completed AWS SSO configuration" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting up Users and Permissions
&lt;/h3&gt;

&lt;p&gt;As of writing this, you can't automatically sync users between AWS and Google (this is being worked on over at OpenID) so we are limited to two options; manually creating the user (which we will go through) and using &lt;a href="https://github.com/awslabs/ssosync" rel="noopener noreferrer"&gt;https://github.com/awslabs/ssosync&lt;/a&gt; to automate the process.&lt;/p&gt;

&lt;p&gt;To manually add users, you will want to follow these instructions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go to &lt;code&gt;Users&lt;/code&gt; in the sidebar of the &lt;code&gt;AWS SSO&lt;/code&gt; service. Then select &lt;code&gt;Add user&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage4.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage4.png" alt="Add user" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the primary Google Workplace email address as the &lt;code&gt;Username&lt;/code&gt; as well as the &lt;code&gt;Email address&lt;/code&gt;, and fill the other fields accordingly. Then hit &lt;code&gt;Next: Groups&lt;/code&gt; to save.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage13.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage13.png" alt="Use Primary Google Workspace email" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As part of this process, we aren't going to be adding groups so we can skip these phases by selecting &lt;code&gt;Add user&lt;/code&gt; in the bottom right.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage25.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage25.png" alt="Skip groups and add user" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The user now needs to be associated with an AWS account. So select &lt;code&gt;AWS accounts&lt;/code&gt; from the left navigation, select the checkbox next to the user, and click &lt;code&gt;Assign users&lt;/code&gt; to attach them to the account.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage8.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage8.png" alt="Assign users" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the next screen select the user again so that we can move on to permissions by clicking &lt;code&gt;Next: Permissions sets&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage10.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage10.png" alt="Select user to assign permissions" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As we haven’t configured and permission sets before we will have to do that now by clicking &lt;code&gt;Create new permission set&lt;/code&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%2Fwww.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage21.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage21.png" alt="Create new permission set" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will be using &lt;code&gt;Use an existing job function policy&lt;/code&gt;, these are like &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html" rel="noopener noreferrer"&gt;AWS managed policies&lt;/a&gt; that you will be aware of if you have configured permissions inside AWS previously. Now &lt;code&gt;Next: Details&lt;/code&gt; to select the policy.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage27.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage27.png" alt="Use an existing job function policy" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From here you can select the policy you want to assign, I'll be using &lt;code&gt;AdministratorAccess&lt;/code&gt; as this is for myself. But you could use &lt;code&gt;PowerUserAccess&lt;/code&gt; as this would allow the user to build whatever they want, but not mess with other users and groups. Then click &lt;code&gt;Next: Tags&lt;/code&gt; to apply this to the user.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage7.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage7.png" alt="Select policy to use" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tags are optional, but they are advised for auditing and search at a later point. But I don't need them so we select &lt;code&gt;Next: Review&lt;/code&gt; to move forward.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage6.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage6.png" alt="Tags are optional" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A quick once over to make sure everything is set correctly, then we can click &lt;code&gt;Create&lt;/code&gt; to do so.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage3.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage3.png" alt="Complete new permissions set" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Back to our &lt;code&gt;Assign Users&lt;/code&gt; screen, we can click the refresh icon to view our permission sets. From here we select the checkbox for the permissions set we want for the user, then select &lt;code&gt;Finish&lt;/code&gt; to apply it.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage9.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage9.png" alt="Assign newly created permissions set" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It'll take a moment to provision, but you should get a Complete screen saying you are done.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage14.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage14.png" alt="Provisioning user complete" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And we are done, now the user can authenticate and log in from Google Workplace using the handy link in the &lt;code&gt;Google apps&lt;/code&gt; selector.&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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage19.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.cloudquery.io%2Fimgs%2Faws-sso-gsuite%2Fimage19.png" alt="Google Apps selector" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;By now you should have AWS SSO configured with Google Workspace as an IdP and you can manage access &amp;amp; permissions to your AWS in the AWS SSO service.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>What is infrastructure drift?</title>
      <dc:creator>Mike Elsmore</dc:creator>
      <pubDate>Tue, 23 Nov 2021 14:05:10 +0000</pubDate>
      <link>https://dev.to/ukmadlz/what-is-infrastructure-drift-3c85</link>
      <guid>https://dev.to/ukmadlz/what-is-infrastructure-drift-3c85</guid>
      <description>&lt;p&gt;I’ve spent most of my career as an application/web developer, so primarily glueing X to Y to do a thing that I love doing! And my exposure to operating through DevOps has been through the lens of an application developer.&lt;/p&gt;

&lt;p&gt;I’ve recently joined &lt;a href="https://www.cloudquery.io/" rel="noopener noreferrer"&gt;CloudQuery&lt;/a&gt;, which is an open-source cloud asset inventory powered by SQL. What this means is that I’m learning more about infrastructure than I’ve ever done before. With that, I’m learning about things like infrastructure as code through &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt; and the concept of infrastructure drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is drift?
&lt;/h2&gt;

&lt;p&gt;Simply put, it’s when the state of your infrastructure is different from the state set by your IaC (Infrastructure-as-Code). For example, if you’ve deployed five lambdas via terraform with the default 128 MB of memory, but now three of them are using 256 MB then you have drift in the states.&lt;/p&gt;

&lt;h2&gt;
  
  
  What causes drift?
&lt;/h2&gt;

&lt;p&gt;My reading has led me to believe that you can have two kinds of drift, the first is like the above which is Resources Managed by IaC and the second is Resources Not Managed by IaC. The first is when all your resources have been deployed by IaC like Terraform or CloudFormation, the second is resources deployed manually or via another process that doesn’t use IaC. The things that cause drift are the same in both cases, either someone or something manually adding or changing individual resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is drift bad?
&lt;/h2&gt;

&lt;p&gt;Not necessarily, things happen in production, you have to tweak and keep things up and alive when apps are on fire. And sometimes, you need resources outside of your IaC, say security tooling that needs oversight etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  But how do you know if you have drifted?
&lt;/h2&gt;

&lt;p&gt;And this is where we come to why I went down this rabbit hole, &lt;a href="https://www.cloudquery.io/blog/announcing-cloudquery-terraform-drift-detection" rel="noopener noreferrer"&gt;CloudQuery now has drift detection&lt;/a&gt; built on top of the open-source cloud asset inventory. You can detect drift via another tool, or via &lt;a href="https://www.hashicorp.com/blog/detecting-and-managing-drift-with-terraform" rel="noopener noreferrer"&gt;Terraform itself&lt;/a&gt;, but these only work against resources managed with Terraform. But with CloudQuery you can see not only the drift from your Terraform state, but also the resources within your Cloud vendor account that aren’t managed as an easy to identify list.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I resolve the drift?
&lt;/h2&gt;

&lt;p&gt;If you head to the &lt;a href="https://docs.cloudquery.io/docs/cli/drift/overview/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for  &lt;code&gt;cloudquery drift scan&lt;/code&gt; you can see an example result, like so:&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="o"&gt;===&lt;/span&gt; DRIFT RESULTS  &lt;span class="o"&gt;===&lt;/span&gt;
5 Resources not managed by Terraform
aws:ec2.ebs_volumes:
- vol-id1
- vol-id2
aws:ec2.instances:
- i-id1
- i-id2
aws:ec2.security_groups:
- sg-id1
93 Resources managed by Terraform &lt;span class="o"&gt;(&lt;/span&gt;equal IDs&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;===&lt;/span&gt; SUMMARY &lt;span class="o"&gt;===&lt;/span&gt;
Total number of resources: 98
- 5 not managed by Terraform
- 93 managed by Terraform &lt;span class="o"&gt;(&lt;/span&gt;equal IDs&lt;span class="o"&gt;)&lt;/span&gt;
- 94.89% covered by Terraform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the resources that are unmanaged by Terraform, as well as any resources that have drifted from your Terraform state. At this point, you can now decide which resources to leave unmanaged by Terraform, or you can follow the &lt;a href="https://learn.hashicorp.com/tutorials/terraform/resource-drift" rel="noopener noreferrer"&gt;Manage Resource Drift&lt;/a&gt; guide in the Terraform documentation to begin managing them and matching the Terraform to production. And for the little piece of extra automation to make your life simpler, you could run &lt;a href="https://docs.cloudquery.io/docs/intro/#iac-infrastructure-as-code-drift-detection" rel="noopener noreferrer"&gt;CloudQuery as part of your CI&lt;/a&gt; to make sure everything is in the right state before or after a deployment.&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
  </channel>
</rss>
