<?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: InstaDevOps</title>
    <description>The latest articles on DEV Community by InstaDevOps (@instadevops).</description>
    <link>https://dev.to/instadevops</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2952358%2F474aa7f4-09cf-409d-891e-cfc4f071d18a.png</url>
      <title>DEV Community: InstaDevOps</title>
      <link>https://dev.to/instadevops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/instadevops"/>
    <language>en</language>
    <item>
      <title>SOC 2 for Startups: The DevOps Work Nobody Warns You About</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Thu, 27 Aug 2026 13:47:04 +0000</pubDate>
      <link>https://dev.to/instadevops/soc-2-for-startups-the-devops-work-nobody-warns-you-about-460a</link>
      <guid>https://dev.to/instadevops/soc-2-for-startups-the-devops-work-nobody-warns-you-about-460a</guid>
      <description>&lt;h2&gt;
  
  
  SOC 2 for Startups: The DevOps Work Nobody Warns You About
&lt;/h2&gt;

&lt;p&gt;The pattern is always the same. A deal is moving, the champion is enthusiastic, and then procurement forwards a 200-row security questionnaire with a cell that says "SOC 2 Type II report attached?" The deal does not die. It just stops moving until you answer.&lt;/p&gt;

&lt;p&gt;At that point most founders start shopping for compliance software, because that is what the ads are for. The software is useful. It is also the smaller half of the job. The larger half is infrastructure work that has to be done by someone who can log into your AWS account and change things.&lt;/p&gt;

&lt;p&gt;Here is what SOC 2 actually asks of your systems, what is engineering versus paperwork, and how long it really takes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What SOC 2 is, stated correctly
&lt;/h3&gt;

&lt;p&gt;SOC 2 is not a certification and there is no such thing as being "SOC 2 certified." It is an attestation: a licensed CPA firm examines your controls and issues a report with their opinion. You do not get a badge from a standards body, you get a PDF from an audit firm that your customer's security team reads.&lt;/p&gt;

&lt;p&gt;The controls are organized under the AICPA Trust Services Criteria. Security (the common criteria) is required in every SOC 2 engagement. Availability, Confidentiality, Processing Integrity, and Privacy are optional categories you include based on what you promise customers. Most startups scope Security only for the first report, sometimes adding Availability and Confidentiality if contracts require it. Every category you add expands the evidence you have to produce, so scope deliberately.&lt;/p&gt;

&lt;p&gt;There are two report types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type I&lt;/strong&gt; describes your controls and whether they are suitably designed as of a specific date. It is a point-in-time snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type II&lt;/strong&gt; tests whether those controls actually operated effectively across an observation window, commonly 3 to 12 months.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enterprise buyers almost always want Type II. Type I is a reasonable interim answer that shows the program exists, and some buyers will accept it with a commitment to Type II. Ask your champion which one actually unblocks the deal before you spend money.&lt;/p&gt;

&lt;p&gt;One more thing worth being clear-eyed about: an auditor does not scan your infrastructure. They ask you for evidence, then test samples of it. If you pulled 12 offboarding tickets, they might test 5. That cuts both ways. It means you cannot be caught by an automated scan you never ran, and it means a single sampled item with no evidence behind it becomes an exception in your report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which criteria map to which infrastructure control
&lt;/h3&gt;

&lt;p&gt;Most of the common criteria that touch engineering come down to a handful of concrete things.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trust Services Criteria theme&lt;/th&gt;
&lt;th&gt;Concrete DevOps control&lt;/th&gt;
&lt;th&gt;Evidence the auditor asks for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Logical access provisioning and removal&lt;/td&gt;
&lt;td&gt;SSO with a single identity provider, no local IAM users with console access, group-based role assignment&lt;/td&gt;
&lt;td&gt;Termination tickets matched to access-removal timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Least privilege for production&lt;/td&gt;
&lt;td&gt;Role assumption with MFA, time-bound elevation, no shared accounts&lt;/td&gt;
&lt;td&gt;IAM policy exports, role trust policies, sample access reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change management&lt;/td&gt;
&lt;td&gt;Pull requests with required review, protected branches, CI checks before deploy&lt;/td&gt;
&lt;td&gt;PR history for a sampled set of production deploys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System monitoring&lt;/td&gt;
&lt;td&gt;Centralized logs, CloudTrail in all regions, alerting with a named on-call owner&lt;/td&gt;
&lt;td&gt;Alert history plus proof someone responded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption in transit and at rest&lt;/td&gt;
&lt;td&gt;TLS enforced at the edge and between services, KMS on volumes, buckets, and databases&lt;/td&gt;
&lt;td&gt;Config rule results, bucket policies, RDS settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup and recovery&lt;/td&gt;
&lt;td&gt;Automated backups with defined retention, plus a documented restore test&lt;/td&gt;
&lt;td&gt;Backup config and a dated restore test record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vulnerability management&lt;/td&gt;
&lt;td&gt;Dependency and image scanning, patch SLAs by severity, tracked remediation&lt;/td&gt;
&lt;td&gt;Scan output and tickets showing fixes inside the SLA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor management&lt;/td&gt;
&lt;td&gt;Inventory of subprocessors with their own reports reviewed&lt;/td&gt;
&lt;td&gt;Vendor list, review dates, signed DPAs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the right-hand column again. Almost every row asks for a record produced over time, not a screenshot of a settings page. That is the thing that surprises people.&lt;/p&gt;

&lt;h3&gt;
  
  
  Turning on the audit trail properly
&lt;/h3&gt;

&lt;p&gt;CloudTrail is the most valuable single piece of evidence infrastructure in an AWS account, and the default setup most startups have is not sufficient. You want a multi-region trail, log file validation enabled, an immutable destination bucket, and KMS encryption.&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"acme-audit-logs-prod"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_versioning"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;versioning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_acls&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_policy&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_public_acls&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;restrict_public_buckets&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_server_side_encryption_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:kms"&lt;/span&gt;
      &lt;span class="nx"&gt;kms_master_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;bucket_key_enabled&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_lifecycle_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"retain-13-months"&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt;
    &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;expiration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudtrail"&lt;/span&gt; &lt;span class="s2"&gt;"org"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"acme-org-trail"&lt;/span&gt;
  &lt;span class="nx"&gt;s3_bucket_name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;kms_key_id&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;is_multi_region_trail&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;include_global_service_events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_log_file_validation&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_logging&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&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;The trail needs a bucket policy allowing the CloudTrail service to write, scoped with a source ARN condition so another account cannot drop logs into your bucket:&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;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sid&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWSCloudTrailAclCheck"&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketAcl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;principals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Service"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cloudtrail.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:SourceArn"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudtrail:${var.region}:${var.account_id}:trail/acme-org-trail"&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="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sid&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWSCloudTrailWrite"&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"${aws_s3_bucket.audit_logs.arn}/AWSLogs/${var.account_id}/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;principals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Service"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cloudtrail.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3:x-amz-acl"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bucket-owner-full-control"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:SourceArn"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudtrail:${var.region}:${var.account_id}:trail/acme-org-trail"&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="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sid&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DenyInsecureTransport"&lt;/span&gt;
    &lt;span class="nx"&gt;effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deny"&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"${aws_s3_bucket.audit_logs.arn}/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;principals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&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;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Bool"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:SecureTransport"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"false"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retention matters here. If your observation window is 6 months and your log retention is 30 days, you cannot produce evidence for month one. Set retention to cover the window plus the audit fieldwork, and set it before the window opens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change management is a GitHub configuration problem
&lt;/h3&gt;

&lt;p&gt;The change management criteria are the easiest to satisfy and the most commonly failed, because teams satisfy them informally. Everyone reviews each other's PRs, but nothing enforces it, so when the auditor samples 25 production deploys and two of them were direct pushes to main at 2am during an incident, those are exceptions.&lt;/p&gt;

&lt;p&gt;Enforce it in configuration so the evidence generates itself:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"github_branch_protection"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;repository_id&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;github_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node_id&lt;/span&gt;
  &lt;span class="nx"&gt;pattern&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt;
  &lt;span class="nx"&gt;enforce_admins&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;require_conversation_resolution&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;required_linear_history&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;allows_force_pushes&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;allows_deletions&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="nx"&gt;required_pull_request_reviews&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;required_approving_review_count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nx"&gt;dismiss_stale_reviews&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;require_code_owner_reviews&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="nx"&gt;required_status_checks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;strict&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"security-scan"&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;Two notes on this. First, &lt;code&gt;enforce_admins = true&lt;/code&gt; is the line people quietly remove when it gets inconvenient, and removing it is exactly what the auditor's sample will surface. Decide now whether you can live with it. Second, if you have an emergency break-glass path, document it as a policy with an after-the-fact review requirement rather than pretending it does not exist. Auditors are far more comfortable with a documented exception process than with an undocumented one they discover in the logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The parts that are genuinely engineering
&lt;/h3&gt;

&lt;p&gt;Strip out everything a template can produce and this is the actual work list:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity consolidation.&lt;/strong&gt; Getting every human out of long-lived IAM users and into SSO with role assumption. This is the single biggest lift for most startups because it touches every tool, and because there is always one legacy service account nobody wants to rotate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offboarding that leaves a trail.&lt;/strong&gt; Not just removing access, but producing a record showing access was removed within your stated SLA. If offboarding is a Slack message to whoever is around, you have no evidence. Wire it to a ticket and to your identity provider's audit log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption sweep.&lt;/strong&gt; Unencrypted EBS volumes, RDS instances created before you cared, S3 buckets without default encryption, internal traffic on plain HTTP. Each is a small fix and there are always more than you expected. AWS Config with managed rules like &lt;code&gt;ENCRYPTED_VOLUMES&lt;/code&gt;, &lt;code&gt;RDS_STORAGE_ENCRYPTED&lt;/code&gt;, and &lt;code&gt;S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED&lt;/code&gt; will find them and, more usefully, keep producing dated compliance results as evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restore testing.&lt;/strong&gt; Backups being configured is not the control. Restoring from them is. You need a dated record of an actual restore, ideally into a scratch environment, with the outcome written down. This is the control I see missing most often, and it is also the one that would save the company in a real incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerting with an owner.&lt;/strong&gt; An alert that fires into a channel nobody reads fails the criteria, and the auditor will find it by asking for the response to a specific alert. You need a defined on-call rotation and a record of triage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vulnerability management with an SLA.&lt;/strong&gt; Scanning is easy. Having written severity-based remediation windows and evidence you met them is the part that takes discipline.&lt;/p&gt;

&lt;p&gt;Everything else (the information security policy, the incident response plan, the risk assessment, the vendor register, the security awareness training) is documentation. Real, necessary documentation that someone has to write and management has to approve, but not engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  What compliance vendors do and do not do
&lt;/h3&gt;

&lt;p&gt;Compliance platforms are worth buying. They give you policy templates, they collect evidence continuously through read-only integrations, they track control status, and they shorten the auditor's fieldwork considerably. If your alternative is a folder of screenshots, buy the tool.&lt;/p&gt;

&lt;p&gt;What they do not do is fix your infrastructure. The platform will tell you that 14 EBS volumes are unencrypted. It will not encrypt them. It will tell you that three people still have IAM access keys. It will not migrate you to SSO. It will flag that no restore test is recorded. It will not run one.&lt;/p&gt;

&lt;p&gt;That gap is the entire project. The dashboard turning green requires someone to plan a volume-by-volume encryption migration with downtime windows, rebuild the access model, and change how deploys work. Budget for that person, whether they are on your team or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  A realistic timeline
&lt;/h3&gt;

&lt;p&gt;For a startup with a normal AWS setup and no prior compliance work, remediation runs 4 to 10 weeks of focused engineering time. That is the part you control and the part that is usually underestimated.&lt;/p&gt;

&lt;p&gt;After remediation you either take a Type I quickly (weeks) or start your Type II observation window. The window itself is calendar time you cannot compress: a 3 month window takes 3 months. Then auditor fieldwork and report issuance adds a few more weeks.&lt;/p&gt;

&lt;p&gt;So "SOC 2 in 90 days" is achievable if it means a Type I plus an open Type II window, and it is not achievable if it means a Type II report in hand. Say that clearly to your customer. Most will accept a Type I and a dated commitment. The ones who will not were probably never going to close this quarter.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five failure modes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No offboarding trail.&lt;/strong&gt; Access was removed, but nothing recorded when or by whom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared production credentials.&lt;/strong&gt; A root key or a shared login in a password manager, which makes every action in the audit log unattributable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups that have never been restored.&lt;/strong&gt; The config exists, the test does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerts nobody acts on.&lt;/strong&gt; Monitoring is in place, response evidence is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starting the observation window before remediating.&lt;/strong&gt; Every day of the window with a broken control is a day of failing evidence, and you cannot retroactively fix it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The order matters. Remediate, verify the controls actually produce evidence, then open the window.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you want help with the infrastructure half
&lt;/h3&gt;

&lt;p&gt;The policies and the evidence platform you can handle in-house or with a compliance vendor. The infrastructure remediation is where teams stall, because it is real engineering on production systems while you are also shipping product. We do that part for clients: identity consolidation, encryption sweeps, change management enforcement, logging and retention, backup restore testing, and the runbooks that keep the controls operating after the auditor leaves. If your deal is stalled on a questionnaire, a short call is usually enough to tell you which of the items above actually apply to your stack.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>compliance</category>
      <category>aws</category>
    </item>
    <item>
      <title>Signs Your Engineering Team Needs DevOps Help</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Wed, 26 Aug 2026 13:47:00 +0000</pubDate>
      <link>https://dev.to/instadevops/signs-your-engineering-team-needs-devops-help-35fg</link>
      <guid>https://dev.to/instadevops/signs-your-engineering-team-needs-devops-help-35fg</guid>
      <description>&lt;h2&gt;
  
  
  Signs Your Engineering Team Needs DevOps Help
&lt;/h2&gt;

&lt;p&gt;Most teams do not decide one morning that they have a DevOps problem. It is quieter. Releases slip by a day, then a week. One person gets pulled into every infrastructure question. An incident gets fixed, and three months later something suspiciously similar takes the site down again.&lt;/p&gt;

&lt;p&gt;Each of those looks like a local problem with a local cause. Together they are usually one thing: nobody owns the path from a laptop to production, so that path decays a little every sprint.&lt;/p&gt;

&lt;p&gt;Below are seven signs, each with a diagnostic to confirm it, why it worsens if ignored, and a first fix you can do this week without buying anything. Where useful I refer to DORA's four key metrics (deployment frequency, lead time for changes, change failure rate, time to restore service), which give shared language for problems otherwise argued about on vibes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 1: Deploys are batched, scheduled, and slightly scary
&lt;/h3&gt;

&lt;p&gt;You recognise this when releases happen on a fixed day, someone is nominated release captain, and nobody ships on Friday afternoon.&lt;/p&gt;

&lt;p&gt;Measure it. If you tag releases, count them per month and count the commits riding along in each:&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;# Releases per month over the last year&lt;/span&gt;
git log &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--simplify-by-decoration&lt;/span&gt; &lt;span class="nt"&gt;--date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s1"&gt;'%Y-%m'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%ad'&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1 year ago'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;

&lt;span class="c"&gt;# How big was the last batch? (commits between the previous tag and HEAD)&lt;/span&gt;
git rev-list &lt;span class="nt"&gt;--count&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git describe &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--abbrev&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 HEAD^&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;..HEAD

&lt;span class="c"&gt;# No tags? Merges to main are a decent proxy for deploys&lt;/span&gt;
git log &lt;span class="nt"&gt;--merges&lt;/span&gt; &lt;span class="nt"&gt;--first-parent&lt;/span&gt; main &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'6 months ago'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s1"&gt;'%Y-%m'&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%ad'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compounds because batch size and fear feed each other. Big releases fail more often, failures make people release less often, and the next batch is bigger still. You also lose bisection: with forty changes in one release, finding the guilty one takes an afternoon.&lt;/p&gt;

&lt;p&gt;First fix: do not rebuild the pipeline, shrink one batch. Take the lowest risk service you own, deploy it on every merge to main, and put anything half-finished behind a flag. Then have someone who did not write the rollback procedure run it against staging while you time them. If it takes over five minutes or needs one specific person, fix that first. Cheap rollback is what makes frequent deploys safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 2: One person is the only route to production
&lt;/h3&gt;

&lt;p&gt;The tell is social, not technical. Infra questions in Slack end up with the same name, releases get planned around one person's holidays, and one laptop has the only working Terraform credentials.&lt;/p&gt;

&lt;p&gt;Confirm it with history:&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;# Who has actually touched infrastructure code in the last year?&lt;/span&gt;
git log &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1 year ago'&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%an'&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  infra/ terraform/ helm/ .github/workflows/ | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one name has ten times the commits of the next, that is your bus factor. Then ask in a retro: if this person vanished for two weeks, what could we not do? If the answers include deploying, rotating a secret, or restoring a backup, that is the finding.&lt;/p&gt;

&lt;p&gt;This compounds unfairly. The person who knows everything is interrupted constantly, so they never get quiet time to write it down or automate it. Then they burn out or take a better offer, and it leaves with them.&lt;/p&gt;

&lt;p&gt;First fix: take the three riskiest operations from that list. For each, someone other than the expert writes the runbook, and someone else again runs it against a non-production environment while the expert watches without touching the keyboard. Every place the runbook is wrong is knowledge that was about to walk out the door.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 3: The same incident keeps coming back with a new name
&lt;/h3&gt;

&lt;p&gt;You have a postmortem that references an earlier postmortem. Disk fills up again, the queue backs up again, and on-call has a folder of alerts they know how to silence but not how to fix.&lt;/p&gt;

&lt;p&gt;Self-check: put six months of incidents in a table with four columns: trigger, contributing cause, agreed action items, and whether those shipped. The last column is the one that matters. Track change failure rate next to it, meaning the share of deploys needing a hotfix or rollback soon after.&lt;/p&gt;

&lt;p&gt;This compounds because unfinished remediation is a promise the team stops believing. Postmortems become ritual, and the alert that has fired five times gets ignored a little faster each time until the real one is missed.&lt;/p&gt;

&lt;p&gt;First fix: freeze new action items until the open ones are closed or explicitly dropped, and dropping is a legitimate recorded choice. Give remediation the same tracking, owner, and deadline as feature work rather than a wiki page. Then convert one recurring alert into a check that runs before deploy, so the failure becomes a build error instead of a page at 2am.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 4: "Works on staging" stopped meaning anything
&lt;/h3&gt;

&lt;p&gt;Staging passes and production breaks. Instance sizes differ, someone fixed something in the cloud console months ago and never wrote it into code, and production has environment variables that exist nowhere in the repo.&lt;/p&gt;

&lt;p&gt;Drift has a direct read. Run a refresh-only plan against production with nothing pending, then compare workloads across environments:&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;# Any diff here is a change someone made outside of code&lt;/span&gt;
terraform plan &lt;span class="nt"&gt;-refresh-only&lt;/span&gt; &lt;span class="nt"&gt;-no-color&lt;/span&gt; | &lt;span class="nb"&gt;tee &lt;/span&gt;drift.txt

&lt;span class="c"&gt;# Compare running images and replica counts between environments&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;ns &lt;span class="k"&gt;in &lt;/span&gt;staging production&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ns&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; get deploy &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    custom-columns&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NAME:.metadata.name,IMAGE:.spec.template.spec.containers[*].image,REPLICAS:.spec.replicas'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Pods with at least one container running with no resource limits&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'
  .items[]
  | select(any(.spec.containers[]; .resources.limits == null))
  | "\(.metadata.namespace)/\(.metadata.name)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compounds because trust is the product staging sells. Once it has lied a few times, people stop treating a green run as evidence and real testing quietly migrates to production, while you keep paying for the environment.&lt;/p&gt;

&lt;p&gt;First fix: eliminate one difference completely. Usually that is the artifact: build the image once, promote that exact image through environments, and inject everything environment specific as configuration. Then run the refresh-only plan nightly and post the output to a channel, so drift surfaces in a day rather than during an incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 5: The cloud bill grows faster than traffic
&lt;/h3&gt;

&lt;p&gt;The bill rises every month and the explanation is always "we're growing". Finance asks which team spent what, and engineering estimates.&lt;/p&gt;

&lt;p&gt;Self-check: divide monthly spend by a business number you track, such as requests served or active users, and plot the ratio over twelve months. Growth raises the total and keeps the ratio flat; waste makes it climb. Then look at ownership, because unowned resources are where waste hides:&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;# Resources with no tags at all are usually resources nobody owns&lt;/span&gt;
aws resourcegroupstaggingapi get-resources &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'ResourceTagMappingList[?length(Tags)==`0`].ResourceARN'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text

&lt;span class="c"&gt;# Unattached EBS volumes still bill every hour&lt;/span&gt;
aws ec2 describe-volumes &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;status,Values&lt;span class="o"&gt;=&lt;/span&gt;available &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Volumes[].{ID:VolumeId,GiB:Size,Created:CreateTime}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compounds because unowned infrastructure never gets deleted. Nobody removes a resource they cannot attribute, in case something depends on it, so waste becomes a permanent floor under your costs until someone mandates a percentage cut under time pressure.&lt;/p&gt;

&lt;p&gt;First fix: require an owner tag on everything created from now on and enforce it in CI for your infrastructure code, so the rule cannot rot. Clean up what those commands found, after checking with whoever created them. Set a budget alert at a threshold that would genuinely surprise you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 6: Onboarding an engineer to infrastructure takes weeks
&lt;/h3&gt;

&lt;p&gt;New hires ship application code in their first few days, then wait a month before anyone lets them near deployment. The setup docs are out of date and the real instructions live in a Slack thread from last year.&lt;/p&gt;

&lt;p&gt;Self-check: give your next new engineer a scripted exercise. From a fresh machine and written docs only, get the app running locally and deploy a trivial change to staging. Nobody helps unless they are fully stuck, and every blocker gets recorded. Do not judge the person: their list is a ranked backlog of everything broken in your setup path.&lt;/p&gt;

&lt;p&gt;This compounds because it caps what hiring buys you: if each new engineer costs weeks of senior time, growing the team makes your seniors less productive before it makes them more productive.&lt;/p&gt;

&lt;p&gt;First fix: take the top three blockers and collapse them into one command, whether that is a make target, a script, or a devcontainer. The goal is one documented command from clean checkout to running environment. Re-run the exercise with the next hire and see if the time drops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign 7: Nobody can answer "what changed?" during an incident
&lt;/h3&gt;

&lt;p&gt;The first twenty minutes of every incident go on asking who deployed what, whether the migration ran, and if anyone touched the load balancer. Nothing shows deploys, config changes, feature flag flips, infrastructure applies, and vendor status on one timeline.&lt;/p&gt;

&lt;p&gt;Self-check: take your most recent incident and, using data alone with no memories and no asking colleagues, reconstruct every change in the twenty-four hours before it started. Time yourself. More than fifteen minutes, or an incomplete answer, means you have no change visibility, and that delay is added to time to restore service on every incident.&lt;/p&gt;

&lt;p&gt;This compounds because it slows recovery exactly when speed matters. It also pushes teams toward change freezes, which feel safe and are not: the eventual unfreeze is a large batch, which takes you back to sign 1.&lt;/p&gt;

&lt;p&gt;First fix: build one change feed. Every deploy emits an event with service, version, commit SHA, actor, and timestamp into one place everyone can see, such as a channel, dashboard annotations, or a small table. Add infrastructure applies and flag changes to the same feed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The seven signs at a glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sign&lt;/th&gt;
&lt;th&gt;What it actually costs&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;First fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batched, scary deploys&lt;/td&gt;
&lt;td&gt;Slow lead time, high change failure rate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Shrink one batch, rehearse rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single point of knowledge&lt;/td&gt;
&lt;td&gt;Total exposure to one person leaving&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Runbooks written and executed by other people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeating incidents&lt;/td&gt;
&lt;td&gt;Senior time burnt twice, alert fatigue&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Close open remediation before opening new items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment drift&lt;/td&gt;
&lt;td&gt;Testing that proves nothing&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Promote one immutable artifact, check drift nightly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bill outgrowing traffic&lt;/td&gt;
&lt;td&gt;Permanent cost floor, panic cuts later&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Owner tags, delete idle resources, budget alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow infra onboarding&lt;/td&gt;
&lt;td&gt;Hiring stops adding throughput&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;One command from checkout to running environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No answer to "what changed?"&lt;/td&gt;
&lt;td&gt;Longer time to restore, freeze reflex&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;One change feed for deploys, applies, and flags&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Severity means how fast the problem stops being recoverable on your own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hire, partner, or fix the process?
&lt;/h3&gt;

&lt;p&gt;If several of these are familiar, the question is what kind of help you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix the process yourself.&lt;/strong&gt; Right call when the signs you recognised are about discipline rather than missing skills: unclosed remediation, no change feed, no rehearsed rollback. Those are days of work, not months. The tradeoff is opportunity cost, and the risk is a migration half done then abandoned when a deadline appears. Only do this if someone gets protected time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hire a platform or DevOps engineer.&lt;/strong&gt; This fits when infrastructure work is continuous, already takes more than half of someone's week, and you can describe the role concretely enough to interview for it. Hiring takes months, seniors are expensive, and you need someone who can technically assess candidates. Watch for the trap in sign 2: one hire into a team with no other infrastructure knowledge recreates the single point of failure under a new name, and lone platform engineers with nobody to review their work tend not to stay long.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bring in a fractional or outsourced partner.&lt;/strong&gt; This fits when you need senior judgement across several areas at once (pipeline, Kubernetes, cost, observability) but not forty hours a week of it, or when the scope is bounded and you need it sooner than hiring could deliver. The tradeoffs are context and dependency: any outsider spends time learning your system, and if nothing is written down while they work, you rented a solution rather than acquired one. Guard against that in the contract. Infrastructure code lives in your repositories, runbooks are written for your engineers, and there is a named internal owner from day one.&lt;/p&gt;

&lt;p&gt;There is a fourth answer nobody sells you: do nothing on purpose. If you are pre-product-market-fit and the system is two services and a database, slow manual deploys are a defensible tradeoff. Just make it a decision you revisit on a date rather than a thing that happened to you.&lt;/p&gt;

&lt;p&gt;If you worked through the self-checks and the answer is a partner, that is what we do at InstaDevOps: senior DevOps on a monthly retainer, with everything we build living in your repositories. Either way, run the diagnostics first. They cost an afternoon and tell you more than any sales conversation.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>cicd</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>The First 90 Days With a DevOps Partner: A Realistic Timeline</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:46:56 +0000</pubDate>
      <link>https://dev.to/instadevops/the-first-90-days-with-a-devops-partner-a-realistic-timeline-2b83</link>
      <guid>https://dev.to/instadevops/the-first-90-days-with-a-devops-partner-a-realistic-timeline-2b83</guid>
      <description>&lt;h2&gt;
  
  
  The First 90 Days With a DevOps Partner: A Realistic Timeline
&lt;/h2&gt;

&lt;p&gt;Most writing about hiring a DevOps partner stops at the sales conversation. What decides whether the money was well spent is the ninety days after the contract is signed, and almost nobody writes that part down honestly.&lt;/p&gt;

&lt;p&gt;This describes what a competent partner does in the first quarter, in what order and why, plus what your side has to provide. That second half matters: the most common reason a good engagement starts badly has nothing to do with the engineers. It is that nobody could grant access, or nobody was empowered to decide.&lt;/p&gt;

&lt;p&gt;None of it is a guarantee. A three person startup on one AWS account moves faster than a fifty person company with four environments nobody fully understands. Treat it as the shape of the work, not a schedule you can hold anyone to.&lt;/p&gt;

&lt;h3&gt;
  
  
  What has to be true before day one
&lt;/h3&gt;

&lt;p&gt;Five things. Miss any of them and the engagement starts slow, and you blame the wrong party.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A named decision maker.&lt;/strong&gt; One person who can approve a change without convening a committee. When a partner finds an unencrypted database snapshot in a public bucket on day three, somebody says yes that afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access, in writing, with a path.&lt;/strong&gt; Read-only on day one, write access to non-production by the end of week one, production change rights when the real work starts. Access delays are the single most common cause of a slow start. If issuing a role takes your security team three weeks, fine, but start before the kickoff call, not after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context on the product.&lt;/strong&gt; Not documentation, which you probably do not have. An hour with whoever knows why the system is shaped as it is: which service is fragile, what broke last quarter, which queue nobody wants to touch. That saves weeks of archaeology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About an hour a week from your engineers.&lt;/strong&gt; Answering questions, reviewing pull requests against your own infrastructure, confirming a change is safe. A partner who needs ten hours a week is not reducing your load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Willingness to freeze change briefly.&lt;/strong&gt; Somewhere in weeks five to eight, deploys pause for a few hours while a pipeline or network path is cut over. Agree in advance when that window can happen. Teams that refuse any freeze drag the cutover across a month of half-finished states, which is riskier than the freeze.&lt;/p&gt;

&lt;h3&gt;
  
  
  Week 1: access, discovery, and a read-only audit
&lt;/h3&gt;

&lt;p&gt;Nothing changes in week one. That is deliberate. A partner who starts editing production on day two either got lucky or is about to cause an incident.&lt;/p&gt;

&lt;p&gt;The work is inventory. Which accounts exist, what runs where, what is managed by code and what was clicked into existence, where the data lives, what the backup situation actually is rather than what the dashboard claims, who holds standing production access and whether any of them have left.&lt;/p&gt;

&lt;p&gt;Access for this is read-only, scoped and time-bound. On AWS that means a role assumed from an external account with an external ID, carrying the managed read-only and security audit policies plus an explicit deny on secret material:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DenySecretMaterialDuringAudit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ssm:GetParameter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ssm:GetParameters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ssm:GetParametersByPath"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"kms:Decrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"lambda:GetFunction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:GetItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:Query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:Scan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attach that alongside &lt;code&gt;arn:aws:iam::aws:policy/ReadOnlyAccess&lt;/code&gt; and &lt;code&gt;arn:aws:iam::aws:policy/SecurityAudit&lt;/code&gt;. An explicit deny beats the allows in a broad managed policy, so the auditor sees that a secret exists, who can reach it, and when it was last rotated, without reading its value or the contents of your buckets and tables. It also settles whether an outside party can read your customer data.&lt;/p&gt;

&lt;p&gt;Week one ends with a written risk list ordered by what would hurt most if it happened tomorrow. Expect discomfort. Common entries: backups never restored, one person who is the only one able to deploy, no alert on the thing that actually pages, credentials in a repository, a certificate expiring soon, one instance holding state nobody replicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done looks like:&lt;/strong&gt; an inventory you did not have, a ranked risk list, agreement on the first three fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Weeks 2 to 4: stop the bleeding
&lt;/h3&gt;

&lt;p&gt;This phase is not architecture. It removes the failure modes that turn a normal Tuesday into a very bad one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backups that are proven, not configured.&lt;/strong&gt; A backup you have never restored is a hypothesis. Verifying one is boring and it is the highest value hour of the quarter. For a custom-format Postgres dump, on a scratch instance, never against production:&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;SNAPSHOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; verify-restore.sh &amp;lt;dump-file&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;SCRATCH_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"restore_check_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

createdb &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRATCH_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'dropdb --if-exists "$SCRATCH_DB"'&lt;/span&gt; EXIT

&lt;span class="nv"&gt;START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;
pg_restore &lt;span class="nt"&gt;--dbname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRATCH_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--no-owner&lt;/span&gt; &lt;span class="nt"&gt;--exit-on-error&lt;/span&gt; &lt;span class="nt"&gt;--jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;ELAPSED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; START &lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"restore completed in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ELAPSED&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s"&lt;/span&gt;

psql &lt;span class="nt"&gt;--dbname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRATCH_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--tuples-only&lt;/span&gt; &lt;span class="nt"&gt;--command&lt;/span&gt; &lt;span class="s2"&gt;"
  SELECT 'rows_in_orders=' || count(*) FROM orders;
  SELECT 'newest_order=' || max(created_at) FROM orders;
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That answers the two questions a green backup dashboard cannot: how long a restore takes, and how much data you would lose. If the first answer is six hours and your recovery objective is one hour, you found a real problem in week two instead of during an outage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerting that corresponds to reality.&lt;/strong&gt; Most teams arrive with either no alerts or two hundred that everyone mutes. Cut them to a small set reflecting user-visible failure, then add the two or three that were missing. Each should mean something and have an owner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access cleanup.&lt;/strong&gt; Remove departed staff, replace long-lived keys with roles, put multi-factor on accounts that can spend money or delete data, move root credentials off one person's laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The obvious cost items.&lt;/strong&gt; Unattached volumes, idle load balancers, old snapshots, oversized non-production instances, logs kept forever at premium tier, environments running full size overnight. None of it needs an architectural change. Deeper savings (right-sizing under real load, commitment purchases, storage class changes) come later, once there is data to decide responsibly. Nobody can tell you the percentage in advance, and anyone who does is guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done looks like:&lt;/strong&gt; a timed restore you watched succeed, alerts the on-call person trusts, no orphaned credentials, easy waste removed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Weeks 5 to 8: the structural work
&lt;/h3&gt;

&lt;p&gt;Now the slower work starts and progress becomes less visible. Warn stakeholders: week six feels quieter than week three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure as code coverage.&lt;/strong&gt; The goal is not rewriting everything in Terraform. It is that anything you could not survive losing is described in code and can be recreated. Usually that means importing existing resources rather than replacing them, starting with networking, IAM, and data stores. Whatever is still clicked into the console is recorded as a known gap, not quietly ignored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline hardening.&lt;/strong&gt; Reproducible builds, pinned dependencies, no secrets in build logs, a staging deploy before production, and a rollback someone has actually executed. Rollback is the part most often skipped, and a pipeline without a tested one is a one-way door.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment parity.&lt;/strong&gt; Not identical environments, which is expensive and rarely worth it. Structural parity: same deployment mechanism, same configuration shape, same runtime versions, differing only in size and data. The point is that a change tested in staging tells you something true about production.&lt;/p&gt;

&lt;p&gt;This is where deeper problems surface. If the application keeps state on local disk, cannot run two copies at once, or opens a database connection per request with no pooling, infrastructure work does not fix it. A good partner says so and scopes it as application work instead of quietly building around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done looks like:&lt;/strong&gt; critical infrastructure reproducible from code, a tested rollback, staging that predicts production, a written list of what is still manual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Weeks 9 to 12: handover, documentation, and proof
&lt;/h3&gt;

&lt;p&gt;An engagement that leaves your team unable to operate the system alone has failed, whatever the dashboards say.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runbooks for the incidents that actually occur.&lt;/strong&gt; Not a generic wiki. One page per realistic failure, written so somebody woken at 3am can follow it:&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;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API latency above SLO&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sev2&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform&lt;/span&gt;
&lt;span class="na"&gt;last_reviewed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-08-12&lt;/span&gt;
&lt;span class="na"&gt;last_tested&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-08-12&lt;/span&gt;

&lt;span class="na"&gt;symptoms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;p95 latency above 800ms for 5 minutes&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout error rate rising&lt;/span&gt;

&lt;span class="na"&gt;first_checks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;dashboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/api-overview (RPS, p95, error rate)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;recent deploys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;last 60 minutes, api and worker&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active connections vs max_connections&lt;/span&gt;

&lt;span class="na"&gt;likely_causes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bad-deploy&lt;/span&gt;
    &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a deploy landed within 30 minutes of the alert&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;roll back to previous release, then investigate&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connection-pool-exhaustion&lt;/span&gt;
    &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active connections at or near max_connections&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restart the worker pool, then raise limits per runbook db-pool&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upstream-provider&lt;/span&gt;
    &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payment provider status page&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;enable degraded checkout mode, notify support&lt;/span&gt;

&lt;span class="na"&gt;escalate_if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;no improvement after 15 minutes&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;customer data may be affected&lt;/span&gt;
&lt;span class="na"&gt;escalate_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;engineering lead, then CTO&lt;/span&gt;

&lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;incident note within 24 hours&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;add the check that would have caught this earlier&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;last_tested&lt;/code&gt; field is the one that matters. A runbook nobody has walked through is fiction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-call readiness.&lt;/strong&gt; A rotation with named people, an escalation path, agreed severity levels, and a short post-incident habit. It does not need to be sophisticated, it needs to exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real handover.&lt;/strong&gt; Your engineers perform a deploy, a rollback, and a restore with the partner watching, not driving. If they cannot, the work is not finished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done looks like:&lt;/strong&gt; tested runbooks, a working rotation, your team running the critical operations unaided.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 90 day timeline at a glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;th&gt;Client involvement&lt;/th&gt;
&lt;th&gt;What "done" looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Week 1&lt;/td&gt;
&lt;td&gt;Access, discovery, read-only audit, risk triage&lt;/td&gt;
&lt;td&gt;Decision maker named, read-only access issued, an hour of product context&lt;/td&gt;
&lt;td&gt;Inventory, ranked risk list, agreed top three fixes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weeks 2 to 4&lt;/td&gt;
&lt;td&gt;Backups verified, alerting fixed, access cleanup, obvious cost waste&lt;/td&gt;
&lt;td&gt;Approvals within a day or two, write access to non-production&lt;/td&gt;
&lt;td&gt;A timed restore observed, trusted alerts, no orphaned credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weeks 5 to 8&lt;/td&gt;
&lt;td&gt;IaC coverage, pipeline hardening, environment parity&lt;/td&gt;
&lt;td&gt;An hour a week, code review, one agreed freeze window&lt;/td&gt;
&lt;td&gt;Critical infrastructure in code, tested rollback, staging that predicts production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weeks 9 to 12&lt;/td&gt;
&lt;td&gt;Runbooks, on-call, documentation, measurement&lt;/td&gt;
&lt;td&gt;Engineers available for handover exercises&lt;/td&gt;
&lt;td&gt;Your team deploys, rolls back, and restores unaided&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How to tell whether it is working
&lt;/h3&gt;

&lt;p&gt;Judge it on observable things, not on how busy the reports look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy frequency and lead time.&lt;/strong&gt; How often you ship, and how long a merged change waits before reaching users. Both come from git and pipeline history, so they are hard to spin. Improvement usually appears in weeks five to eight, not before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to restore.&lt;/strong&gt; How long from "something is broken" to "it works again", from real incidents where possible, drills where not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeat incidents.&lt;/strong&gt; The same failure twice means the first fix addressed a symptom. A falling repeat rate beats a falling incident count, which is easy to influence by counting fewer things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost trend, with context.&lt;/strong&gt; Absolute spend is noisy while you are growing. Look at cost per unit of what you sell, and whether spend moves with usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bus factor.&lt;/strong&gt; How many people can deploy, restore the database, and rotate a credential. If that is still one at day ninety, the engagement missed its main job.&lt;/p&gt;

&lt;p&gt;Baseline all of these in week one. Without a baseline, every later conversation about progress is opinion.&lt;/p&gt;

&lt;h3&gt;
  
  
  When this does not work
&lt;/h3&gt;

&lt;p&gt;Some engagements fail, and the reasons repeat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access never fully arrives.&lt;/strong&gt; Read-only in week two, write access in week six, production access never. Everything stays advisory. If your process cannot grant a scoped external role, resolve that before signing, or accept that you are buying consulting rather than execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No decision maker.&lt;/strong&gt; Recommendations pile up awaiting approval from someone permanently in another meeting. Ninety days pass, the audit was accurate, almost nothing shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ownership is unclear internally.&lt;/strong&gt; Two teams each believe the other owns deployment. A partner cannot arbitrate that, only you can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real problem is the application.&lt;/strong&gt; If the system cannot scale horizontally, or one database is doing work that needs a queue, infrastructure work reduces symptoms without removing the cause. An honest partner says this in week two. Rearchitecting a product is a different engagement with a different timeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expectations were never written down.&lt;/strong&gt; "Improve our DevOps" is not a scope. Three named outcomes, each with a definition of done, is. Vague briefs produce disappointing quarters even when the work was good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change is not permitted.&lt;/strong&gt; A team that will not pause deploys, accept a pipeline change, or adopt new tooling has bought an auditor. That is a legitimate purchase, but it should be a conscious one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The short version
&lt;/h3&gt;

&lt;p&gt;A partner controls perhaps two thirds of the first ninety days. The rest is your access, your decisions, and an hour a week.&lt;/p&gt;

&lt;p&gt;If you are evaluating a partner now, the useful question is not what they will build. It is what they expect from you in week one, and what they do when the answer is not more infrastructure. If you want to talk through what your own first ninety days would realistically look like, we are happy to map it out before anything is signed.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>sre</category>
      <category>aws</category>
    </item>
    <item>
      <title>DevOps for Series A Startups: What to Build First</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Mon, 24 Aug 2026 17:11:51 +0000</pubDate>
      <link>https://dev.to/instadevops/devops-for-series-a-startups-what-to-build-first-5efl</link>
      <guid>https://dev.to/instadevops/devops-for-series-a-startups-what-to-build-first-5efl</guid>
      <description>&lt;h2&gt;
  
  
  DevOps for Series A Startups: What to Build First
&lt;/h2&gt;

&lt;p&gt;A Series A changes the shape of your engineering problems before it changes the size of your team. The product still works. The same three people still know how everything fits together. But now there is a board deck with a hiring plan in it, a couple of enterprise deals that come with security questionnaires, and a runway long enough that "we will fix it later" has quietly become a decision rather than an accident.&lt;/p&gt;

&lt;p&gt;The pressure to "do DevOps properly" usually arrives as a shopping list: Kubernetes, a service mesh, multi-region, an internal developer platform, a platform team. Almost none of that is the right first move. What follows is what actually matters between roughly 8 and 30 engineers, what to postpone, and how to think about hiring versus outsourcing versus doing nothing yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually breaks at this stage
&lt;/h3&gt;

&lt;p&gt;The failures are boring and predictable, and they are almost never about scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment becomes contested.&lt;/strong&gt; At five engineers, one person deploys when the tests look fine. At fifteen, two teams want to ship on the same afternoon, someone reverts someone else's migration, and the fix is a Slack thread instead of a process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One person is the runbook.&lt;/strong&gt; There is a founding engineer who knows why the NAT gateway is configured that way, which environment variable the worker actually reads, and how to restart the thing that gets stuck on Sundays. That person is now a manager, or interviewing candidates, or on a plane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environments drift.&lt;/strong&gt; Staging was created by hand in the console eighteen months ago. Production was created by hand too, differently. Nobody can say with confidence what the difference is, so staging stops being evidence of anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credentials sprawl.&lt;/strong&gt; A shared AWS IAM user with an access key pasted into three laptops and a CI provider. A database password in a pinned Slack message. Nobody has rotated anything, and nobody can tell you who has access to customer data. This is the item that blocks enterprise deals, not your uptime numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nobody has restored a backup.&lt;/strong&gt; Automated snapshots are enabled, which is not the same as knowing you can bring the database back. Untested backups are a belief, not a control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerting is either silent or useless.&lt;/strong&gt; Either you find out about outages from customers, or you have 40 alerts a day and everyone has muted the channel.&lt;/p&gt;

&lt;p&gt;Each of those is fixable in days or weeks. None of them requires Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The minimum viable platform
&lt;/h3&gt;

&lt;p&gt;Six things. In this order.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. One repeatable path to production
&lt;/h4&gt;

&lt;p&gt;Every deploy goes through the same pipeline, triggered by a merge to the main branch, with no human running commands on their laptop. The pipeline should use short-lived cloud credentials, not a stored access key. On AWS and GitHub, that means OIDC.&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&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="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;   &lt;span class="c1"&gt;# required for OIDC&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&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;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&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="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::111122223333:role/github-deploy&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ecr&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v2&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="s"&gt;Build, push, deploy&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.ecr.outputs.registry }}&lt;/span&gt;
          &lt;span class="na"&gt;TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;set -euo pipefail&lt;/span&gt;
          &lt;span class="s"&gt;IMAGE="$REGISTRY/api:$TAG"&lt;/span&gt;
          &lt;span class="s"&gt;docker build -t "$IMAGE" .&lt;/span&gt;
          &lt;span class="s"&gt;docker push "$IMAGE"&lt;/span&gt;

          &lt;span class="s"&gt;NEW_TD=$(aws ecs register-task-definition \&lt;/span&gt;
            &lt;span class="s"&gt;--cli-input-json "$(jq --arg img "$IMAGE" \&lt;/span&gt;
              &lt;span class="s"&gt;'.containerDefinitions[0].image = $img' task-def.json)" \&lt;/span&gt;
            &lt;span class="s"&gt;--query 'taskDefinition.taskDefinitionArn' --output text)&lt;/span&gt;

          &lt;span class="s"&gt;aws ecs update-service --cluster prod --service api \&lt;/span&gt;
            &lt;span class="s"&gt;--task-definition "$NEW_TD"&lt;/span&gt;
          &lt;span class="s"&gt;aws ecs wait services-stable --cluster prod --services api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter more than the platform choice. The image tag is the commit SHA, so any running container maps back to exact source. And the job waits for the service to stabilise, so a red pipeline means a failed deploy rather than a successful upload.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Infrastructure as code, starting with the parts that hurt
&lt;/h4&gt;

&lt;p&gt;You do not need to import every hand-made resource on day one. Codify the things that are painful to recreate and dangerous to change: networking, IAM, databases, DNS, and the CI role itself. Leave the S3 bucket someone made in 2024 for later.&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;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"acme-tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;use_lockfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;# S3-native locking, no DynamoDB table needed&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_openid_connect_provider"&lt;/span&gt; &lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://token.actions.githubusercontent.com"&lt;/span&gt;
  &lt;span class="nx"&gt;client_id_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"github_assume"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;effect&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nx"&gt;principals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Federated"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_openid_connect_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringEquals"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Scope to one repo and one branch. Without this condition any&lt;/span&gt;
    &lt;span class="c1"&gt;# GitHub repository in the world can assume the role.&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;test&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"StringLike"&lt;/span&gt;
      &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"repo:acme/api:ref:refs/heads/main"&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="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"github_deploy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github-deploy"&lt;/span&gt;
  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_assume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sub&lt;/code&gt; condition is the one people get wrong. A trust policy that only checks the audience is effectively public.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Observability that answers three questions
&lt;/h4&gt;

&lt;p&gt;Not dashboards. Three questions: is it broken, what changed, and where is the time going. In practice that is structured logs with a request ID, error tracking wired to a real channel, and latency and error rate per endpoint. A hosted tool is the right answer here. Running your own metrics and log storage at this size costs more engineering time than it saves in licence fees, and it fails at the worst moment because nobody owns it.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. On-call that a person can survive
&lt;/h4&gt;

&lt;p&gt;Two or three people in rotation, a written escalation path, and a rule that every page must be actionable. If an alert fires and the response is "yeah, that happens", either fix the thing or delete the alert. A rotation of one is not a rotation, it is a single point of failure with a phone.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Backups you have actually restored
&lt;/h4&gt;

&lt;p&gt;Snapshots enabled is step one. Step two is a restore rehearsal you run on a schedule, ideally in CI, so it cannot rot.&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;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;SNAPSHOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws rds describe-db-snapshots &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; prod-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--snapshot-type&lt;/span&gt; automated &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'reverse(sort_by(DBSnapshots, &amp;amp;SnapshotCreateTime))[0].DBSnapshotIdentifier'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

aws rds restore-db-instance-from-db-snapshot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; restore-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-snapshot-identifier&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SNAPSHOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-class&lt;/span&gt; db.t4g.medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-publicly-accessible&lt;/span&gt;

aws rds &lt;span class="nb"&gt;wait &lt;/span&gt;db-instance-available &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; restore-test

&lt;span class="c"&gt;# Prove the data is real, not just that the instance booted.&lt;/span&gt;
psql &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESTORE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"select count(*), max(created_at) from orders;"&lt;/span&gt;

aws rds delete-db-instance &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; restore-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skip-final-snapshot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write down how long that took. That number is your recovery time objective, and it is the honest answer when a customer asks.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Access control you can explain
&lt;/h4&gt;

&lt;p&gt;SSO for the cloud console, individual identities instead of shared logins, a secrets manager instead of environment variables copied between people, and no long-lived access keys in CI. This is unglamorous and it is the work that turns a security questionnaire from a two-week scramble into a form-filling exercise.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is premature right now
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing&lt;/th&gt;
&lt;th&gt;Why teams want it&lt;/th&gt;
&lt;th&gt;Why it can wait&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;It is what "real" infrastructure looks like&lt;/td&gt;
&lt;td&gt;Unless you already run many services or need its scheduling, ECS Fargate, Cloud Run, or a managed PaaS gets you there with a fraction of the operational surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service mesh&lt;/td&gt;
&lt;td&gt;Observability and mTLS between services&lt;/td&gt;
&lt;td&gt;You probably have three services. A load balancer and good logging cover it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-region active-active&lt;/td&gt;
&lt;td&gt;An enterprise prospect asked&lt;/td&gt;
&lt;td&gt;It multiplies data consistency problems and cost. A tested restore and a documented recovery objective answer the actual question&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal developer platform&lt;/td&gt;
&lt;td&gt;Deploys feel manual&lt;/td&gt;
&lt;td&gt;A platform is an abstraction over repeated pain. Have the repetition first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dedicated platform team&lt;/td&gt;
&lt;td&gt;The org chart says so&lt;/td&gt;
&lt;td&gt;Pulling two engineers off product to run a platform for twelve people is usually net negative until roughly 25 to 40 engineers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Splitting the monolith&lt;/td&gt;
&lt;td&gt;It feels crowded&lt;/td&gt;
&lt;td&gt;Service boundaries chosen before you understand the domain become distributed versions of the same coupling, with network calls added&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern: these are all solutions to coordination problems that appear when many teams share infrastructure. At Series A you usually have one or two teams. Buying the solution before you have the problem means paying the operating cost without the benefit.&lt;/p&gt;

&lt;h3&gt;
  
  
  A realistic order of operations
&lt;/h3&gt;

&lt;p&gt;Roughly a quarter of work, mostly sequential because each step makes the next one cheaper.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 1 to 2.&lt;/strong&gt; Access control and secrets. Kill shared credentials and long-lived keys. This is also the highest-value item for sales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 2 to 4.&lt;/strong&gt; CI/CD to production for the main service. One path, commit-tagged images, automatic rollback or at least a one-command revert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 4 to 7.&lt;/strong&gt; Infrastructure as code for networking, IAM, data stores, DNS. Rebuild staging from that code to prove it works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 6 to 9.&lt;/strong&gt; Observability and alerting. Error tracking, structured logs, a small set of alerts tied to customer-visible symptoms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 9 to 11.&lt;/strong&gt; Backups, restore rehearsal, written recovery objectives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 11 to 13.&lt;/strong&gt; On-call rotation, runbooks for the five things that actually break, a short incident review habit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cost control belongs somewhere in there too, but as a habit rather than a project: tagging, a budget alarm, and a monthly look at the top five line items.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hire, outsource, or defer
&lt;/h3&gt;

&lt;p&gt;There is no universally correct answer, and the honest framing is about what you are buying.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Fits when&lt;/th&gt;
&lt;th&gt;Real cost&lt;/th&gt;
&lt;th&gt;Where it goes wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hire a platform or DevOps engineer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Infrastructure work is continuous, not a project. You have a technical hiring loop and someone senior to manage them&lt;/td&gt;
&lt;td&gt;Salary plus recruiting time, typically 2 to 4 months from opening the role to productive output&lt;/td&gt;
&lt;td&gt;You hire one person, they become the single point of failure you were trying to remove, and they get bored if the work is 80% maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Outsource to a service or fractional team&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You have a known gap to close in a defined window, and product engineers are the constraint&lt;/td&gt;
&lt;td&gt;Monthly fee, plus your own time on context transfer&lt;/td&gt;
&lt;td&gt;Nobody on your side owns the outcome. Without a named internal counterpart, you get artefacts you cannot maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Defer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deploys are not blocked, no customer is asking for security evidence, fewer than about eight engineers&lt;/td&gt;
&lt;td&gt;Compounding interest. The cleanup gets more expensive as more is built on the shortcut&lt;/td&gt;
&lt;td&gt;It stops being a choice. You notice when an outage or a deal forces the work at the worst possible time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A pattern that works well: outsource the build, own the operation. Bring in help to set up the pipeline, the Terraform, the alerting and the on-call structure, then have your own engineers run it day to day with the option to call for help on the hard parts. That avoids the two common failure modes, which are hiring a specialist to do six weeks of setup and then two years of waiting, and handing your infrastructure to an outside party with no internal understanding of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  When not to buy this from us
&lt;/h3&gt;

&lt;p&gt;Straightforwardly: if you have fewer than about eight engineers and your app is a single service on a managed platform, you probably do not need us yet. Spend two engineer-weeks on CI/CD, a secrets manager, and a restore test, and get back to product.&lt;/p&gt;

&lt;p&gt;If you already have a strong infrastructure-minded engineer with time to spend, you do not need us either. Give them the list above and a quarter.&lt;/p&gt;

&lt;p&gt;And if what you actually want is someone to hand a problem to permanently, with no internal owner, that arrangement does not work well with anyone, us included. External help is good at building and unblocking. It is a poor substitute for someone inside the company who cares whether the system stays healthy.&lt;/p&gt;

&lt;p&gt;If none of those apply, and you are staring at a quarter of infrastructure work that would come straight out of your product roadmap, that is the case where bringing in a fractional DevOps team is the cheaper trade. We are happy to look at what you have and tell you which parts of the list you can skip, including the parts we would not charge you for.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>startup</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>What a DevOps Audit Actually Covers (and What It Finds)</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Mon, 24 Aug 2026 15:53:36 +0000</pubDate>
      <link>https://dev.to/instadevops/what-a-devops-audit-actually-covers-and-what-it-finds-361n</link>
      <guid>https://dev.to/instadevops/what-a-devops-audit-actually-covers-and-what-it-finds-361n</guid>
      <description>&lt;h2&gt;
  
  
  What a DevOps Audit Actually Covers (and What It Finds)
&lt;/h2&gt;

&lt;p&gt;An infrastructure audit is an awkward purchase. The deliverable is a document, the value is judgement rather than hours, and you cannot tell a good one from a bad one until it lands on your desk. Meanwhile your team already has a list of things they know are wrong, so the obvious question is whether you are paying someone to write that list down again.&lt;/p&gt;

&lt;p&gt;Here is what a serious audit inspects, how it is run, what it costs you in your own team's attention, and when to skip it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What "audit" should mean here
&lt;/h3&gt;

&lt;p&gt;An audit is a bounded, read-only assessment of your infrastructure, delivery process, and operational practice, ending in a prioritised set of findings with severity, effort, and business consequence attached to each one.&lt;/p&gt;

&lt;p&gt;Three words there carry weight. &lt;strong&gt;Bounded&lt;/strong&gt;: a defined scope and end date, usually one to three weeks, not an open engagement. &lt;strong&gt;Read-only&lt;/strong&gt;: the auditor changes nothing, so the assessment cannot itself cause an incident. &lt;strong&gt;Prioritised&lt;/strong&gt;: the output ranks findings, which is what separates an audit from a scanner report. If a proposal lacks all three, you are buying something else.&lt;/p&gt;

&lt;h3&gt;
  
  
  The domains a real audit covers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Evidence examined&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Spend by service and tag, Savings Plan and reserved instance coverage, unattached volumes, idle load balancers, cross-AZ and egress transfer, non-production running outside working hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security posture&lt;/td&gt;
&lt;td&gt;Ingress rules open to the internet, encryption at rest and in transit, where secrets live and who can read them, image and dependency scan results, patch levels, findings sitting unread in the provider's own security services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability and single points of failure&lt;/td&gt;
&lt;td&gt;Availability zone spread, replica counts, database failover config, health check and timeout settings, shared dependencies every service touches, six months of incident tickets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD maturity&lt;/td&gt;
&lt;td&gt;Pipeline definitions, time from merge to production, pipeline failure and rerun rate, approval gates, whether a rollback path exists and has been used, build reproducibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IaC coverage and drift&lt;/td&gt;
&lt;td&gt;Share of live resources actually managed by Terraform or CloudFormation, refresh-only plan results, console changes visible in audit logs, module and provider version pinning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Metric, log, and trace coverage per service, retention settings and their cost, the alert inventory, ratio of alerts fired to incidents declared, which dashboards were opened during the last outage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access control&lt;/td&gt;
&lt;td&gt;IAM policies with wildcard actions or resources, long-lived static keys, separation of human and machine identities, break-glass procedure, evidence that leavers were actually removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup and DR&lt;/td&gt;
&lt;td&gt;Schedules and retention, date of the last successful restore test, stated RTO and RPO versus what the setup can achieve, cross-region and cross-account copies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation and bus factor&lt;/td&gt;
&lt;td&gt;Runbook coverage for the top ten alerts, realistic onboarding time for a new engineer, count of systems only one person can deploy or debug&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cost and security are the domains buyers ask for. They are also the ones scanners handle best, which makes them the least valuable part of a human audit. An experienced reviewer earns the fee on reliability, CI/CD, drift, and bus factor, because all four require reading your actual code and talking to your actual engineers.&lt;/p&gt;

&lt;p&gt;Reliability work is a hunt for single points of failure nobody has written down: the one NAT gateway, the single-writer database with a replica nobody has ever promoted, the shared Redis that four services treat as optional and one treats as mandatory, the certificate renewed by hand each year by someone who left in March.&lt;/p&gt;

&lt;p&gt;CI/CD maturity is measured, not described. How long from merge to production, honestly, including waiting for a human to click approve? What fraction of pipeline runs fail for reasons unrelated to the change? Can you roll back, and when did you last do it? A team with a fast, boring pipeline can fix almost anything else. A team without one keeps regenerating the same problems.&lt;/p&gt;

&lt;p&gt;Drift is the gap between your Terraform and your reality. Most teams over-report their infrastructure-as-code coverage, because they count the resources in state and not the resources in the account.&lt;/p&gt;

&lt;h3&gt;
  
  
  How a good audit is actually run
&lt;/h3&gt;

&lt;p&gt;Four inputs, in roughly this order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-only access.&lt;/strong&gt; A dedicated IAM role with a managed read-only policy, ideally with an external ID and an expiry date, plus read access to repositories and observability tooling. If a provider asks for admin, ask why. The usual reason is laziness on their side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interviews.&lt;/strong&gt; Forty-five minutes each with the people who carry the pager, plus the engineering lead and, if cost is in scope, whoever owns the bill. Engineers tell you in ten minutes what a scanner cannot find in a week: which deploy everyone dreads, which alert gets muted, which service nobody wants to touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tooling scans.&lt;/strong&gt; Provider-native tools first, since you already pay for them, then whatever the auditor brings. Fast and largely automated, which is why it should not be the bulk of the fee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual review.&lt;/strong&gt; Reading Terraform modules, pipeline definitions, Kubernetes manifests, and incident write-ups. This is where the fee goes, and it cannot be shortened without hollowing out the result.&lt;/p&gt;

&lt;p&gt;A clean sequence: access and scans in the first two or three days, interviews in the first week, manual review through the second, a draft walkthrough, then the written deliverable. The walkthrough matters. Findings written without a chance to say "we know, that is deliberate, here is why" produce documents that get ignored.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it costs you in your team's time
&lt;/h3&gt;

&lt;p&gt;The invoice is not the whole price. Budget six to ten hours of engineering time: an hour to provision access, three to five hours of interviews across two or three people, an hour or two of follow-up questions, and an hour for the walkthrough. A provider who needs zero time from your team is producing a scanner dump.&lt;/p&gt;

&lt;p&gt;On the invoice side, the drivers are scope (how many domains), environment and account count, whether Kubernetes is involved, how much of the infrastructure is code you can read versus clicked-together resources, and depth (a survey versus a review that reads every module). Review work scales with surface area rather than headcount, so a multi-account setup with several clusters costs considerably more than one account with a handful of services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checks you can run before you pay anyone
&lt;/h3&gt;

&lt;p&gt;Run these first. If they come back clean, an audit will find less than you hope. If they come back ugly, you have a scope.&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;# --- AWS: cost and encryption basics (all read-only) ---&lt;/span&gt;

&lt;span class="c"&gt;# Unencrypted EBS volumes&lt;/span&gt;
aws ec2 describe-volumes &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;encrypted,Values&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Volumes[].{ID:VolumeId,Size:Size,AZ:AvailabilityZone,State:State}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# Volumes detached from everything and still billed every month&lt;/span&gt;
aws ec2 describe-volumes &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;status,Values&lt;span class="o"&gt;=&lt;/span&gt;available &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Volumes[].{ID:VolumeId,Size:Size,Created:CreateTime}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# Elastic IPs not associated with anything (charged hourly while idle)&lt;/span&gt;
aws ec2 describe-addresses &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Addresses[?AssociationId==`null`].{IP:PublicIp,Alloc:AllocationId}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# Security group rules open to the whole internet&lt;/span&gt;
aws ec2 describe-security-groups &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`]]].{ID:GroupId,Name:GroupName,VPC:VpcId}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="c"&gt;# Buckets with no public access block configured at all&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;b &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;aws s3api list-buckets &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Buckets[].Name'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;aws s3api get-public-access-block &lt;span class="nt"&gt;--bucket&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"NO PUBLIC ACCESS BLOCK: &lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Credential hygiene: key age, MFA, unused passwords, in one report&lt;/span&gt;
aws iam generate-credential-report &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
aws iam get-credential-report &lt;span class="nt"&gt;--query&lt;/span&gt; Content &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; | column &lt;span class="nt"&gt;-s&lt;/span&gt;, &lt;span class="nt"&gt;-t&lt;/span&gt;     &lt;span class="c"&gt;# macOS: use `base64 -D`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Kubernetes checks cover the reliability side. Missing resource limits cause noisy-neighbour incidents, and single-replica deployments survive precisely until the next node rotation.&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;# --- Kubernetes: reliability and hygiene (read-only) ---&lt;/span&gt;

&lt;span class="c"&gt;# Containers with no memory limit set&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'
  .items[] as $p | $p.spec.containers[]
  | select(.resources.limits.memory == null)
  | "\($p.metadata.namespace)/\($p.metadata.name)  container=\(.name)"'&lt;/span&gt;

&lt;span class="c"&gt;# Deployments running exactly one replica&lt;/span&gt;
kubectl get deploy &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; custom-columns&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NS:.metadata.namespace,NAME:.metadata.name,REPLICAS:.spec.replicas'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$3 == 1 {print $1"/"$2}'&lt;/span&gt;

&lt;span class="c"&gt;# Namespaces with workloads but no PodDisruptionBudget&lt;/span&gt;
&lt;span class="nb"&gt;comm&lt;/span&gt; &lt;span class="nt"&gt;-23&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get deploy &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get pdb &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;--no-headers&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Images pinned to a moving tag&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;':latest'&lt;/span&gt;

&lt;span class="c"&gt;# Pods not required to run as a non-root user&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'
  .items[] | select((.spec.securityContext.runAsNonRoot // false) != true)
  | "\(.metadata.namespace)/\(.metadata.name)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the drift check, which is usually the most uncomfortable of the three. A refresh-only plan compares state to reality without proposing or applying any change.&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;# Detect drift without changing anything.&lt;/span&gt;
&lt;span class="c"&gt;# Exit code 0 = state matches reality, 2 = drift found, 1 = error.&lt;/span&gt;
terraform plan &lt;span class="nt"&gt;-refresh-only&lt;/span&gt; &lt;span class="nt"&gt;-detailed-exitcode&lt;/span&gt;

&lt;span class="c"&gt;# How much of the account is actually managed here?&lt;/span&gt;
terraform state list | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'^aws_instance\.'&lt;/span&gt;
aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;instance-state-name,Values&lt;span class="o"&gt;=&lt;/span&gt;running &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'length(Reservations[].Instances[])'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the second number is much larger than the first, your infrastructure-as-code coverage is a story rather than a fact. Repeat for security groups, RDS instances, and IAM roles.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the deliverable should look like
&lt;/h3&gt;

&lt;p&gt;A finding is useful only when it carries four things: what is wrong, what happens if it stays wrong, how much work the fix is, and what to do first. A usable format is one page per finding, with a severity, an effort estimate in engineer-days, the business consequence in plain language, and the specific resource or file it applies to.&lt;/p&gt;

&lt;p&gt;Ahead of the findings, expect a short executive summary an engineering leader can forward without editing, and a sequenced remediation plan: this week, this quarter, this year, with dependencies noted (you cannot enforce pipeline policy before the pipeline exists).&lt;/p&gt;

&lt;p&gt;Do not accept a PDF export of a scanner, five hundred unranked medium-severity rows, or recommendations that all point toward the auditor's own retainer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What audits commonly find
&lt;/h3&gt;

&lt;p&gt;These are patterns, not measurements, and every environment differs. Commonly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure-as-code coverage below what the team believed, with the gap concentrated in resources created during incidents.&lt;/li&gt;
&lt;li&gt;Non-production environments sized like production and running around the clock.&lt;/li&gt;
&lt;li&gt;Backups configured and never restored, so the recovery time objective is a guess.&lt;/li&gt;
&lt;li&gt;One person who is the only one who can deploy a particular service.&lt;/li&gt;
&lt;li&gt;An alert set nobody trusts, the noisiest ones routed to a muted channel.&lt;/li&gt;
&lt;li&gt;Long-lived access keys belonging to former employees or to a service replaced two years ago.&lt;/li&gt;
&lt;li&gt;A pipeline that deploys but cannot roll back, so every bad release becomes a forward-fix under pressure.&lt;/li&gt;
&lt;li&gt;Log and metric retention left at the default and never revisited, quietly becoming a top line on the bill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are exotic. They accumulate because they are individually small and collectively nobody's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telling a genuine audit from a sales exercise
&lt;/h3&gt;

&lt;p&gt;Signals it is real: read-only access with a defined expiry, a written scope naming the domains covered, interviews with your engineers, a draft walkthrough before the final document, findings that include things to stop doing as well as start, and several recommendations you can implement yourself with no outside help.&lt;/p&gt;

&lt;p&gt;Signals it is a sales exercise: a fixed-length report promised before anyone has looked at your environment, no engineer interviews, generic best-practice findings that never name your own resources, severity ratings that are all high, and a remediation plan whose every step requires the auditor.&lt;/p&gt;

&lt;p&gt;The strongest test: ask what the audit would say if your environment turned out to be in good shape. A provider who cannot describe that outcome is not selling an assessment.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you do not need one
&lt;/h3&gt;

&lt;p&gt;If your team can already name the top five problems, agrees on their order, and knows how to fix them, an audit will mostly confirm the list. That is a capacity problem, and the money is better spent on the fixing than on the finding. Buying an audit here is often really about getting an outside voice to repeat what your engineers have been saying. That is sometimes a legitimate need, but call it what it is and buy less of it.&lt;/p&gt;

&lt;p&gt;Skip it too if you are pre-launch with a couple of services and one environment, if you are mid-migration and the target architecture is already decided, or if you audited within the last year and have shipped nothing structural since.&lt;/p&gt;

&lt;p&gt;The case is strongest when you inherited the environment, when the people who built it have gone, when the spend moved and nobody can explain why, when a compliance review or due diligence is coming, or when incidents are rising and no one can say which risk to spend the next quarter on.&lt;/p&gt;

&lt;p&gt;If you want an outside read on any of that, we run scoped, read-only infrastructure reviews and hand back prioritised findings you can act on with or without us. A short call is enough to work out whether an audit is the right purchase for where you are, or whether you should skip straight to the fixing.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>cloud</category>
      <category>security</category>
    </item>
    <item>
      <title>Terraform Cloud Workspaces: Team Collaboration &amp; Remote State</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Thu, 20 Aug 2026 13:47:43 +0000</pubDate>
      <link>https://dev.to/instadevops/terraform-cloud-workspaces-team-collaboration-remote-state-1okn</link>
      <guid>https://dev.to/instadevops/terraform-cloud-workspaces-team-collaboration-remote-state-1okn</guid>
      <description>&lt;h2&gt;
  
  
  Terraform Cloud and Workspaces: Remote Execution, Policy Enforcement, and Team Workflows
&lt;/h2&gt;

&lt;p&gt;Terraform Cloud transforms Terraform from a CLI tool that engineers run on their laptops into a collaborative platform with remote state, remote execution, policy enforcement, and audit trails. Running terraform apply locally works for a solo engineer, but breaks down with teams - who ran the last apply, what version of the code was used, were the right variables set, did anyone review the plan? Terraform Cloud solves these problems with a centralized execution environment.&lt;/p&gt;

&lt;p&gt;Workspaces in Terraform Cloud map to environments or components. A typical structure uses separate workspaces for each environment (&lt;code&gt;app-production&lt;/code&gt;, &lt;code&gt;app-staging&lt;/code&gt;, &lt;code&gt;app-development&lt;/code&gt;) sharing the same Terraform code but with different variable values. VCS-driven workspaces automatically trigger plans when code is pushed to a connected Git branch - push to &lt;code&gt;main&lt;/code&gt; triggers the production workspace, push to &lt;code&gt;develop&lt;/code&gt; triggers staging. Run triggers chain workspaces together: when the networking workspace applies successfully, it triggers the compute workspace that depends on it.&lt;/p&gt;

&lt;p&gt;Policy enforcement with Sentinel (or OPA for the free tier) is the governance layer that distinguishes Terraform Cloud from running the CLI with a remote backend. Policies check plans before apply - reject any plan that creates public S3 buckets, require specific tags on all resources, limit instance sizes in non-production environments, and mandate encryption on all storage. The approval workflow adds human review: plans require approval from designated team members before apply, with the full plan output visible in the UI. For teams, this creates a self-service infrastructure workflow where developers can deploy within guardrails without needing infrastructure team involvement for every change.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Need help setting up Terraform Cloud?&lt;/strong&gt; &lt;a href="https://instadevops.com" rel="noopener noreferrer"&gt;InstaDevOps&lt;/a&gt; implements Terraform Cloud workflows with proper governance for growing engineering teams. &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;Book a free consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Python for DevOps: Automation Scripts, APIs &amp; CLI Tools</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:47:38 +0000</pubDate>
      <link>https://dev.to/instadevops/python-for-devops-automation-scripts-apis-cli-tools-3535</link>
      <guid>https://dev.to/instadevops/python-for-devops-automation-scripts-apis-cli-tools-3535</guid>
      <description>&lt;h2&gt;
  
  
  Python for DevOps: Automating AWS, Kubernetes, and CI/CD with Boto3 and Fabric
&lt;/h2&gt;

&lt;p&gt;Python has become the default scripting language for DevOps automation. Bash scripts work for simple tasks, but once you need error handling, API integrations, data processing, or testable code, Python's ecosystem is unmatched. Boto3 provides complete AWS API coverage, the official Kubernetes Python client manages clusters programmatically, and libraries like Fabric, Paramiko, and Invoke handle SSH and local command execution.&lt;/p&gt;

&lt;p&gt;Boto3 is the most commonly used DevOps Python library. Automating AWS operations - cleaning up unattached EBS volumes, rotating IAM access keys older than 90 days, generating cost reports across accounts, or managing EC2 instances based on tags - becomes straightforward with Boto3's resource and client interfaces. Use sessions with assumed roles for cross-account automation, paginators for listing large result sets, and waiters for operations that take time (instance launch, stack creation). For Kubernetes, the official Python client mirrors kubectl functionality: list pods, scale deployments, apply manifests, and watch for resource changes programmatically.&lt;/p&gt;

&lt;p&gt;The key to maintainable DevOps Python is treating scripts like production code. Use Click or Typer for CLI argument parsing instead of sys.argv. Structure code in functions and classes, not monolithic scripts. Write unit tests with pytest and mock AWS calls with moto. Use virtual environments and pin dependencies. For recurring tasks, package your scripts as Python packages with proper entry points rather than running raw .py files. Fabric excels for SSH-based automation when you need to run commands on remote servers - deploying to legacy infrastructure, collecting diagnostics, or managing services that are not yet containerized.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Need DevOps automation expertise?&lt;/strong&gt; &lt;a href="https://instadevops.com" rel="noopener noreferrer"&gt;InstaDevOps&lt;/a&gt; builds custom automation tooling for AWS, Kubernetes, and CI/CD workflows. &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;Book a free consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>automation</category>
    </item>
    <item>
      <title>Feature Flags &amp; Progressive Delivery: Ship Faster, Safer</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Tue, 18 Aug 2026 13:47:34 +0000</pubDate>
      <link>https://dev.to/instadevops/feature-flags-progressive-delivery-ship-faster-safer-3o8</link>
      <guid>https://dev.to/instadevops/feature-flags-progressive-delivery-ship-faster-safer-3o8</guid>
      <description>&lt;h2&gt;
  
  
  Feature Flags and Progressive Delivery: LaunchDarkly, Unleash, and Flagsmith
&lt;/h2&gt;

&lt;p&gt;Feature flags decouple deployment from release. You deploy code to production with new features hidden behind flags, then gradually enable them for specific users, percentages of traffic, or entire cohorts. This eliminates the high-stakes big-bang release - if a feature causes problems, you toggle it off instantly without rolling back a deployment. Progressive delivery extends this pattern with automated, metrics-driven rollout: start at 1% of traffic, monitor error rates and latency, automatically increase to 10%, then 50%, then 100% if all metrics stay healthy.&lt;/p&gt;

&lt;p&gt;The three major platforms serve different segments. LaunchDarkly is the enterprise standard with real-time flag evaluation, sophisticated targeting rules, and audit logging - but it is expensive at scale. Unleash is open-source and self-hosted, offering feature toggles, gradual rollouts, and A/B testing with full control over your data. Flagsmith provides a middle ground with both cloud-hosted and self-hosted options, a clean API, and built-in analytics. For most startups, Unleash gives you 90% of the functionality at a fraction of the cost.&lt;/p&gt;

&lt;p&gt;The implementation pattern matters as much as the tool. Wrap flag evaluations in a thin abstraction layer so you can swap providers without changing application code. Use flag naming conventions that indicate lifecycle (e.g., &lt;code&gt;release-new-checkout&lt;/code&gt;, &lt;code&gt;experiment-pricing-page&lt;/code&gt;, &lt;code&gt;ops-circuit-breaker-payments&lt;/code&gt;). Set expiration dates on temporary release flags and enforce cleanup - stale flags accumulate quickly and become technical debt. For progressive delivery, integrate your flag platform with your observability stack: the flag system reads metrics from Prometheus or Datadog and automatically advances or rolls back the rollout based on error budgets.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Need help with deployment strategies?&lt;/strong&gt; &lt;a href="https://instadevops.com" rel="noopener noreferrer"&gt;InstaDevOps&lt;/a&gt; implements progressive delivery pipelines with feature flags and automated rollouts. &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;Book a free consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>deployment</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Managed DevOps Services: What's Actually Included (and What to Watch For)</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Mon, 17 Aug 2026 13:47:30 +0000</pubDate>
      <link>https://dev.to/instadevops/managed-devops-services-whats-actually-included-and-what-to-watch-for-4k6l</link>
      <guid>https://dev.to/instadevops/managed-devops-services-whats-actually-included-and-what-to-watch-for-4k6l</guid>
      <description>&lt;h2&gt;
  
  
  Managed DevOps Services: What's Actually Included (and What to Watch For)
&lt;/h2&gt;

&lt;p&gt;'Managed DevOps services' sounds precise but means wildly different things depending on who is selling it. One provider means a full production operations partnership; another means they will set up a pipeline once and disappear. Before you commit budget, know what a genuinely complete managed service covers and where the gaps hide in the fine print.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it should include.&lt;/strong&gt; A complete engagement covers the full lifecycle: cloud infrastructure defined in Infrastructure as Code (usually Terraform), CI/CD pipelines with safe deployment and rollbacks, monitoring and alerting (typically Prometheus and Grafana), ongoing cloud cost optimization, security hardening with least-privilege IAM and secrets management, and incident support. A narrow provider who only does pipelines will send you shopping again the moment you need observability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to watch for in the fine print.&lt;/strong&gt; Incident support can mean 24/7 paged response, or 'we will look at it during business hours.' These are radically different products, so get specific about hours and response times. Watch for vendor lock-in where configuration lives on the provider's systems instead of your cloud account. Watch for undefined scope, where 'includes CI/CD support' has no volume limit and your work quietly gets deprioritized. And watch for hourly billing that turns a predictable managed service into an unpredictable invoice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define capacity clearly.&lt;/strong&gt; Models that state throughput are easier to reason about. For example, our plans are structured around active requests: the Startup tier at $2,999/month handles one request at a time and the Business tier at $4,999/month handles two, both with unlimited queued requests, no hourly billing, and roughly 48-hour turnaround. That tells you exactly what you are buying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirm who holds the pager.&lt;/strong&gt; A managed service is only as good as the people behind it. Make sure senior engineers, not rotating junior contractors, are responsible for your environment, with real AWS depth alongside Kubernetes, CI/CD, Terraform, monitoring, and security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A checklist before you sign:&lt;/strong&gt; Is the full lifecycle covered? What does incident support include and during what hours? Does everything live in your account and repositories? Is pricing flat? Is capacity defined? Who are the actual engineers? How do you leave cleanly? Answer those and you will know whether a managed service is a real operations partnership or a thin wrapper around a one-time setup.&lt;/p&gt;




&lt;p&gt;At &lt;a href="https://instadevops.com" rel="noopener noreferrer"&gt;InstaDevOps&lt;/a&gt; we deliver managed DevOps for startups with senior, AWS-focused engineers on flat monthly plans from $2,999/month, with roughly 48-hour turnaround and everything built in your own cloud account. &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;Book a free 15-minute consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>cloud</category>
    </item>
    <item>
      <title>DevOps Engineer Salary vs Retainer: The Real 2026 Cost Comparison</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Sun, 16 Aug 2026 13:47:27 +0000</pubDate>
      <link>https://dev.to/instadevops/devops-engineer-salary-vs-retainer-the-real-2026-cost-comparison-425j</link>
      <guid>https://dev.to/instadevops/devops-engineer-salary-vs-retainer-the-real-2026-cost-comparison-425j</guid>
      <description>&lt;h2&gt;
  
  
  DevOps Engineer Salary vs Retainer: The Real 2026 Cost Comparison
&lt;/h2&gt;

&lt;p&gt;When founders price out DevOps, they anchor on one number: the salary. But salary is the smallest part of what a full-time hire actually costs, and it tells you almost nothing about whether hiring is the right move at your stage. Here is an honest, numbers-first comparison for 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The salary is just the sticker price.&lt;/strong&gt; In 2026, a mid-to-senior DevOps engineer in the US commands a base salary of roughly $140,000 to $185,000. That is the number founders quote to each other, and it is also the number that hides the most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fully loaded cost is far higher.&lt;/strong&gt; Add payroll taxes and benefits (25 to 40 percent on top of base), recruiting fees (15 to 25 percent of first-year salary), equipment, and management overhead, and a $160,000 base becomes roughly $210,000 to $260,000 in year one. Then add the hidden costs: two to four months to hire, weeks of ramp time at full salary, single-point-of-failure risk when your one engineer takes vacation or quits, and underutilization during the quiet stretches between infrastructure projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What a retainer actually costs.&lt;/strong&gt; A DevOps retainer flips the model. For reference, our plans are $2,999/month for the Startup tier (one active request at a time) and $4,999/month for the Business tier (two active requests), both with unlimited queued requests, no hourly billing, and pause or resume. That is a fully loaded annual cost of roughly $36,000 to $60,000 with zero recruiting fees, zero benefits overhead, and no ramp time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When hiring full-time still wins.&lt;/strong&gt; A full-time engineer makes sense when infrastructure is your core product, when you have enough continuous deep work to keep a specialist busy every single week, or when compliance requires employees. For most early and growth-stage teams, the math favors flexible external capacity until the volume genuinely justifies a dedicated headcount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the numbers honestly.&lt;/strong&gt; Take the base salary, add 30 to 40 percent for benefits and taxes, add amortized recruiting cost, and add the opportunity cost of months without a solution. Compare that to twelve months of a retainer at your expected workload, then ask whether you truly have 40-plus hours of DevOps work every week or whether it comes in bursts. That question decides it for most founders.&lt;/p&gt;




&lt;p&gt;At &lt;a href="https://instadevops.com" rel="noopener noreferrer"&gt;InstaDevOps&lt;/a&gt; we give startups senior, AWS-focused DevOps on a flat monthly retainer from $2,999/month, with no recruiting fees and no ramp time. &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;Book a free 15-minute consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloudcomputing</category>
      <category>startup</category>
    </item>
    <item>
      <title>How to Choose a DevOps as a Service Provider: A 2026 Buyer's Checklist</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Sat, 15 Aug 2026 13:47:23 +0000</pubDate>
      <link>https://dev.to/instadevops/how-to-choose-a-devops-as-a-service-provider-a-2026-buyers-checklist-2i7g</link>
      <guid>https://dev.to/instadevops/how-to-choose-a-devops-as-a-service-provider-a-2026-buyers-checklist-2i7g</guid>
      <description>&lt;h2&gt;
  
  
  How to Choose a DevOps as a Service Provider: A 2026 Buyer's Checklist
&lt;/h2&gt;

&lt;p&gt;Hiring a full-time DevOps engineer takes months and costs a small fortune. DevOps as a Service has become the practical alternative for startups and scale-ups that need production-grade infrastructure without building an internal platform team. But the market is crowded and quality varies wildly, so this checklist walks through exactly what to evaluate before you sign.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with the problem, not the provider.&lt;/strong&gt; Write down what you actually need done in the next 90 days: a CI/CD pipeline that keeps breaking, an AWS bill that has doubled, a Kubernetes cluster no one wants to touch, or a founding engineer who has quietly become the accidental infrastructure owner. Being specific helps you filter broad 'digital transformation' consultants from the providers who ship real infrastructure changes every week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check who actually does the work.&lt;/strong&gt; Many agencies sell you a senior architect in the pitch, then hand execution to junior contractors. You want senior engineers doing the hands-on work, with real depth in your cloud provider. AWS experience matters most for the majority of startups, ideally backed by Kubernetes, CI/CD, Terraform, monitoring, and security skills on the same team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer flat pricing over hourly.&lt;/strong&gt; Hourly billing rewards slow work. A flat monthly price aligns incentives and makes budgeting predictable. For reference, our own plans run $2,999/month for the Startup tier (one active request at a time) and $4,999/month for the Business tier (two active requests), both with unlimited queued requests, no hourly billing, and roughly 48-hour turnaround.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insist on no lock-in.&lt;/strong&gt; Everything a provider builds should live in your repositories and your cloud account, described in readable Infrastructure as Code and runbooks. If a provider keeps configuration on their own machines or refuses to hand over Terraform state, walk away. A confident provider makes leaving a non-event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red flags to watch for:&lt;/strong&gt; vague deliverables, no named engineers, proprietary tooling lock-in, and pressure to sign a long contract before you have seen any output. Confidence looks like month-to-month; insecurity looks like a 12-month commitment up front.&lt;/p&gt;




&lt;p&gt;At &lt;a href="https://instadevops.com" rel="noopener noreferrer"&gt;InstaDevOps&lt;/a&gt; we provide senior, AWS-focused DevOps as a Service on flat monthly plans from $2,999/month, with no hourly billing and pause or resume whenever your needs change. &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;Book a free 15-minute consultation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>startup</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>When to Hire Your First DevOps Engineer (and What to Do Before You Do)</title>
      <dc:creator>InstaDevOps</dc:creator>
      <pubDate>Fri, 14 Aug 2026 13:47:19 +0000</pubDate>
      <link>https://dev.to/instadevops/when-to-hire-your-first-devops-engineer-and-what-to-do-before-you-do-e24</link>
      <guid>https://dev.to/instadevops/when-to-hire-your-first-devops-engineer-and-what-to-do-before-you-do-e24</guid>
      <description>&lt;h2&gt;
  
  
  Most teams hire their first DevOps engineer too early or too late
&lt;/h2&gt;

&lt;p&gt;There is no magic headcount number that says it is time. Some 5-person teams genuinely need a dedicated infrastructure person; some 30-person teams get by fine with developers who share the load. The right question is not how big are we but &lt;strong&gt;how much operational work exists, who is doing it now, and what is it costing us&lt;/strong&gt;. This guide walks through the signals that actually matter, what the role should own, what it costs, and the cheaper options worth trying first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signals that you genuinely need one
&lt;/h2&gt;

&lt;p&gt;Watch for these patterns. One alone is rarely decisive; three or more together usually means the workload is real.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploys are scary.&lt;/strong&gt; Releases happen rarely because they are manual, fragile, or only one person understands them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your best developers are drowning in infrastructure.&lt;/strong&gt; Senior engineers spend hours per week on CI failures, cloud config, and firefighting instead of product work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incidents have no owner.&lt;/strong&gt; When production breaks, it is a scramble because nobody clearly owns monitoring, alerting, or on-call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud spend is climbing and nobody can explain it.&lt;/strong&gt; The AWS bill grows every month and no one has time to audit it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance is knocking.&lt;/strong&gt; A customer wants SOC 2, or you need auditable access controls, and there is no system for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding a new environment takes days.&lt;/strong&gt; Spinning up staging or a new region is a manual, error-prone ordeal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you nodded at most of these, the operational load has outgrown ad-hoc ownership. That does not automatically mean a full-time hire, but it does mean something has to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the role should actually own
&lt;/h2&gt;

&lt;p&gt;Before you write a job description, get clear on scope. A first DevOps engineer is a generalist who typically owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipelines&lt;/strong&gt;: fast, reliable, automated build, test, and deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as code&lt;/strong&gt;: reproducible environments in Terraform, Pulumi, or CloudFormation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: metrics, logs, tracing, and alerting that catch problems before customers do&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud architecture and cost&lt;/strong&gt;: right-sizing, networking, security groups, and spend control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security baseline&lt;/strong&gt;: secrets management, least-privilege access, patching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident response&lt;/strong&gt;: runbooks, on-call structure, and post-incident reviews&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a wide surface area. Be honest that one person cannot do all of it deeply at once. Rank these by pain and let the first hire focus there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;A dedicated DevOps or SRE engineer is one of the more expensive engineering hires. In the US, total compensation for a mid-to-senior engineer commonly lands between 130,000 and 200,000 USD per year, plus benefits, equipment, and recruiting cost. Add the hidden costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hiring lead time&lt;/strong&gt;: senior DevOps roles often take 2 to 4 months to fill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ramp time&lt;/strong&gt;: even a strong hire needs weeks to learn your stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bus factor&lt;/strong&gt;: with one person, vacations and departures are real risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this means do not hire. It means the decision should be deliberate, because the fully loaded cost of a first DevOps engineer easily exceeds 180,000 USD per year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheaper alternatives worth trying first
&lt;/h2&gt;

&lt;p&gt;If the workload is real but not yet a full 40 hours a week, or you cannot afford the ramp time, consider these before committing to a permanent hire.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fractional or part-time senior help
&lt;/h3&gt;

&lt;p&gt;A senior engineer for part of their time can set up your pipelines, harden security, and get cloud costs under control without a six-figure commitment. This works especially well for the initial setup phase, where you need senior judgment more than 40 weekly hours. Our &lt;a href="https://instadevops.com/fractional-devops-engineer/" rel="noopener noreferrer"&gt;fractional DevOps engineer&lt;/a&gt; page covers how that arrangement works.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. DevOps as a service on a retainer
&lt;/h3&gt;

&lt;p&gt;If the need is ongoing operations rather than a one-time setup, a monthly retainer with an external team gives you continuous coverage at a predictable cost, often less than half a full-time salary. See our &lt;a href="https://instadevops.com/devops-monthly-retainer/" rel="noopener noreferrer"&gt;DevOps monthly retainer&lt;/a&gt; breakdown and the broader case in our &lt;a href="https://instadevops.com/alternative-to-hiring-devops/" rel="noopener noreferrer"&gt;alternative to hiring DevOps&lt;/a&gt; guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Upskill a developer plus better tooling
&lt;/h3&gt;

&lt;p&gt;Sometimes the honest answer is that you do not need a specialist yet. A motivated developer plus managed platforms (managed Kubernetes, managed databases, a good CI provider, and a platform-as-a-service host) can carry a small team a surprisingly long way. Managed services trade money for reduced operational burden, which can defer the hire entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should just hire in-house
&lt;/h2&gt;

&lt;p&gt;Be clear about when the alternatives stop making sense. Hire full-time when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DevOps work is clearly a full-time load and will only grow&lt;/li&gt;
&lt;li&gt;Infrastructure is core to your product, not a supporting function&lt;/li&gt;
&lt;li&gt;You need someone deeply embedded in daily engineering decisions&lt;/li&gt;
&lt;li&gt;Compliance or customer requirements demand a dedicated internal owner&lt;/li&gt;
&lt;li&gt;You are past the setup phase and into continuous scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, a permanent hire gives you the deep context and availability that outside help cannot fully match. Many teams sequence it: use fractional or retainer help to get the foundation right, then hire in-house once the workload is steady and well understood. Our &lt;a href="https://instadevops.com/hire-devops-engineer/" rel="noopener noreferrer"&gt;hire a DevOps engineer&lt;/a&gt; page covers what to look for when you reach that point.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple framework
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Measure the load.&lt;/strong&gt; For two weeks, track hours your team spends on infra, CI, incidents, and cloud. That number tells you if it is a full-time job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match the model to the load.&lt;/strong&gt; Under 15 hours a week and mostly setup: fractional. Steady ongoing ops under a full role: retainer. A clear full-time load that is core to the product: hire.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect the knowledge.&lt;/strong&gt; Whatever you choose, insist on infrastructure as code, documentation, and cloud accounts your company owns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want help figuring out which bucket you are in, InstaDevOps offers senior DevOps on a monthly retainer as one option: Startup at 2,999 USD per month, Business at 4,999 USD per month, roughly 48-hour turnaround, pause anytime. It is one path, and we will tell you honestly if a full-time hire or a short fractional engagement fits you better. Book a free 15-minute call at &lt;a href="https://calendly.com/instadevops/15min" rel="noopener noreferrer"&gt;calendly.com/instadevops/15min&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>startup</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
