[{"data":1,"prerenderedAt":3197},["ShallowReactive",2],{"content-/ewahl/2025-05/escape_deployment_hell":3},{"article":4,"all":2310},{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"tags":11,"excerpt":10,"image":15,"publishDate":16,"body":17,"_type":2301,"_id":2302,"_source":2303,"_file":2304,"_stem":2305,"_extension":2306,"author":2307},"/ewahl/2025-05/escape_deployment_hell","2025-05",false,"","Escape Deployment Hell: IaC, CDK, Ephemeral Environments, and the Pragmatic Path to Platform Power","Another Friday afternoon, another deployment fire. If this sounds familiar, you're not alone. On too many projects, the chasm between application code and infrastructure management breeds manual configuration nightmares, crippling complexity, and agonizingly slow development cycles. But what if your team could sidestep this chaos, focusing on building features instead of constantly battling deployment gremlins?",[12,13,14],"devops","aws","python","/ewahl/2025-05/img/deployment_hell.png","2025-05-13",{"type":18,"children":19,"toc":2275},"root",[20,27,32,57,73,107,113,118,131,136,148,154,185,191,205,218,223,230,303,342,347,353,367,1428,1433,1438,1462,1468,1482,1488,1529,1535,1565,1571,1601,1607,1653,1659,1689,1695,1725,1731,1761,1766,1772,1777,1782,1787,1792,1797,1803,1808,1813,1819,1824,1830,1835,2165,2171,2269],{"type":21,"tag":22,"props":23,"children":24},"element","p",{},[25],{"type":26,"value":10},"text",{"type":21,"tag":22,"props":28,"children":29},{},[30],{"type":26,"value":31},"The solution lies in integrating Infrastructure as Code (IaC) with your application code, to support ephemeral environments. When every feature can be realistically tested in isolation and infrastructure changes are transparent, tested, and automated the benefits are transformative:",{"type":21,"tag":33,"props":34,"children":35},"ul",{},[36,42,47,52],{"type":21,"tag":37,"props":38,"children":39},"li",{},[40],{"type":26,"value":41},"Slash Debugging Time: Catch infrastructure and compatibility issues immediately.",{"type":21,"tag":37,"props":43,"children":44},{},[45],{"type":26,"value":46},"Boost Team Velocity: Eliminate bottlenecks and enable parallel development without conflicts.",{"type":21,"tag":37,"props":48,"children":49},{},[50],{"type":26,"value":51},"Keep Crises Small: Switch focus easily without disrupting existing goals.",{"type":21,"tag":37,"props":53,"children":54},{},[55],{"type":26,"value":56},"Enhance Release Confidence: Test so that \"it passed QA\" actually means \"it will work in production\" without creating a QA bottleneck.",{"type":21,"tag":22,"props":58,"children":59},{},[60,62,71],{"type":26,"value":61},"This approach is more than just good DevOps; it's the heart of modern platform engineering. Platform engineering is essential to prevent agile methodologies from becoming chaotic, as it equips teams with the framework and discipline to build and manage ephemeral environments vital for isolated testing and rapid, controlled iteration. That impact is why Gartner identifies Platform Engineering as a top technology trend (Gartner, 2023: ",{"type":21,"tag":63,"props":64,"children":68},"a",{"href":65,"rel":66},"https://www.gartner.com/en/articles/gartner-top-10-strategic-technology-trends-for-2024",[67],"nofollow",[69],{"type":26,"value":70},"Top 10 Strategic Technology Trends for 2024",{"type":26,"value":72},"). By leveraging tools like AWS CDK, you can achieve many of the coveted benefits of a full-scale Internal Developer Platform (IDP) without the overwhelming cost and complexity, putting true platform power within reach.",{"type":21,"tag":74,"props":75,"children":76},"blockquote",{},[77,84],{"type":21,"tag":78,"props":79,"children":81},"h2",{"id":80},"key-ideas",[82],{"type":26,"value":83},"Key Ideas",{"type":21,"tag":33,"props":85,"children":86},{},[87,92,97,102],{"type":21,"tag":37,"props":88,"children":89},{},[90],{"type":26,"value":91},"End Frustrating Software Releases: Many teams struggle with slow, error-prone software releases because their testing setups create bottlnecks and don't match the real, live system.",{"type":21,"tag":37,"props":93,"children":94},{},[95],{"type":26,"value":96},"Develop and Test New Features Perfectly with \"Pop-Up\" Systems: Called \"Ephemeral Environments\" built using Infrastructure as Code (IaC) these are a temporary replica of the live system created for every new feature. This allows each update to be tested thoroughly and safely in isolation before it goes live, after which the temporary system disappears.",{"type":21,"tag":37,"props":98,"children":99},{},[100],{"type":26,"value":101},"Build Your System Setup with Code, Not Clicks: The magic behind these pop-up systems is defining all the necessary IT infrastructure (servers, databases, networks) using code that lives alongside application code in the same repo — called \"Infrastructure as Code\" or IaC. This automates the creation of consistent, reliable development and testing environments every time.",{"type":21,"tag":37,"props":103,"children":104},{},[105],{"type":26,"value":106},"A Practical Path to Power: Instead of building or buying a massive, complex internal system — often called an Internal Developer Platform — tools like AWS Cloud Development Kit (CDK) allow DevOps teams and app developers to use their existing coding skills to set up and manage these automated environments. This makes the benefits of faster, more reliable deployments accessible to more teams without overwhelming cost or effort.",{"type":21,"tag":78,"props":108,"children":110},{"id":109},"the-painful-path-to-ephemeral-environments",[111],{"type":26,"value":112},"The Painful Path to Ephemeral Environments",{"type":21,"tag":22,"props":114,"children":115},{},[116],{"type":26,"value":117},"My passion for ephemeral environments isn't just theoretical; it's forged from real-world deployment pain early in my career. I vividly recall working on a Python web application where our traditional approach to testing using a single, IT maintained, shared staging environment led to constant headaches, stemming from two core, intertwined problems.",{"type":21,"tag":22,"props":119,"children":120},{},[121,123,129],{"type":26,"value":122},"First, there is the issue of environment divergence. Our staging environment was ",{"type":21,"tag":124,"props":125,"children":126},"em",{},[127],{"type":26,"value":128},"supposed",{"type":26,"value":130}," to be a twin of the production configuration, the place where we could confidently say, \"If it works here, it'll work live.\" But because both staging and, at times, production received manual tweaks or patches that weren't always replicated across both, they would inevitably drift apart. These subtle inconsistencies were a minefield. So, as developers, we'd push our code from our local setups to the mainline branch, and it would be deployed to staging for \"final\" validation. But occasionally, tests would pass on staging, and everything would seem fine. The real pain would come later: we'd promote that 'staging-approved' code to production, only for critical bugs to suddenly surface. These were the issues that left us scrambling to figure out a hotfix or rollback to rescue production.",{"type":21,"tag":22,"props":132,"children":133},{},[134],{"type":26,"value":135},"The other issue is the chaos and bottlneck invited by our single, mainline-focused testing envrionment. We would merge multiple, distinct features together into the mainline branch for testing. If a problem surfaced there — and it often did — everything was entangled, leading to frantic pre-release 'cherry-picking' sessions or just outright delaying the whole batch. Yet, the relentless pressure to ship sometimes meant we would need to push those changes to production with less testing than they really needed. I still feel that knot of anxiety tightening in my stomach, knowing we were rolling the dice. And the 'day-after' scrambles: as panicked messages came in about 'Users are seeing unexpected errors!'. It was always all hands on deck—a blur of late nights and urgent calls, trying to diagnose issues that had been masked by our process, then facing the horrible choice: attempt a risky, rushed hotfix, or execute a full, rollback that took working features down with the bad. This exhausting cycle shattered client trust and turned release schedule into guesswork.",{"type":21,"tag":22,"props":137,"children":138},{},[139,141,146],{"type":26,"value":140},"After enduring these twin challenges, the migration to ephermeral environments created wonderful new results. 'Works on my machine' finally translated reliably to 'works in integration,' because each test environment was an automated replica of production. And because every feature was tested thoroughly in its own dedicated space ",{"type":21,"tag":124,"props":142,"children":143},{},[144],{"type":26,"value":145},"before",{"type":26,"value":147}," merging to mainline, the bottlenecks and release fire-drills vanished. Our release predictability dramatically improved, restoring client trust and confidence. I have no interest in ever returning to the stress of unreliable staging, mainline blockages, and anxious stakeholder conversations.",{"type":21,"tag":78,"props":149,"children":151},{"id":150},"the-allure-and-cost-of-the-internal-developer-platform-idp",[152],{"type":26,"value":153},"The Allure — and Cost — of the Internal Developer Platform (IDP)",{"type":21,"tag":22,"props":155,"children":156},{},[157,159,166,168,174,176,183],{"type":26,"value":158},"The desire to solve these kinds of problems at scale often leads organizations to consider creating an Internal Developer Platform (IDP). IDPs aim to streamline developer workflows by providing centralized tooling, self-service capabilities for infrastructure, standardized deployment pathways, and reduced cognitive load, thereby significantly improving Developer Experience (DevEx) — aligning nicely with ",{"type":21,"tag":63,"props":160,"children":163},{"href":161,"rel":162},"https://tag-app-delivery.cncf.io/whitepapers/platform-eng-maturity-model/",[67],[164],{"type":26,"value":165},"CNCF's Platform Engineering Maturity Model",{"type":26,"value":167},". However, building and maintaining a truly comprehensive IDP is a massive undertaking. As Atlassian describes, an IDP is \"a self-service interface between developers and the underlying infrastructure, tools, and processes required to build, deploy, and manage software applications... ",{"type":21,"tag":169,"props":170,"children":171},"span",{},[172],{"type":26,"value":173},"providing",{"type":26,"value":175}," developers with a unified interface or portal where they can access everything they need to build, test, and deploy applications. This platform is typically tailored to a company's specific needs, integrating various tools, services, and workflows into a cohesive, user-friendly system.\" (Source: ",{"type":21,"tag":63,"props":177,"children":180},{"href":178,"rel":179},"https://www.atlassian.com/developer-experience/internal-developer-platform",[67],[181],{"type":26,"value":182},"Atlassian - Internal Developer Platform",{"type":26,"value":184},"). This vision of a unified, all-encompassing system often requires dedicated platform teams, significant upfront investment, and ongoing operational effort, making it a substantial commitment. This is where a more pragmatic, developer-centric approach can offer a compelling alternative.",{"type":21,"tag":78,"props":186,"children":188},{"id":187},"a-pragmatic-path-aws-cdk-for-developer-empowered-platforms",[189],{"type":26,"value":190},"A Pragmatic Path: AWS CDK for Developer-Empowered Platforms",{"type":21,"tag":22,"props":192,"children":193},{},[194,196,203],{"type":26,"value":195},"You don't always need a heavyweight IDP to achieve many of its core benefits. The philosophy behind tools like AWS CDK is to empower teams and developers by leveraging their existing skill sets. As highlighted in ",{"type":21,"tag":63,"props":197,"children":200},{"href":198,"rel":199},"https://aws.amazon.com/blogs/opensource/working-backwards-the-story-behind-the-aws-cloud-development-kit/",[67],[201],{"type":26,"value":202},"Working backwards: The story behind the AWS Cloud Development Kit",{"type":26,"value":204},":",{"type":21,"tag":74,"props":206,"children":207},{},[208],{"type":21,"tag":22,"props":209,"children":210},{},[211,216],{"type":21,"tag":169,"props":212,"children":213},{},[214],{"type":26,"value":215},"CDK",{"type":26,"value":217}," is an open source software development framework that lets developers define cloud application resources using familiar programming languages, so they do not have to learn a new domain-specific language. The AWS CDK lets developers manage those resources in the same way that they manage their application code, ensuring visibility, reproducibility, and repeatability, and it is part of how modern applications treat their infrastructure as code.",{"type":21,"tag":22,"props":219,"children":220},{},[221],{"type":26,"value":222},"This approach is key to making Infrastructure as Code truly accessible and is a game-changer for practices like creating dynamic, ephemeral deployment environments. This drastically reduces the boilerplate and complexity compared to writing raw AWS CloudFormation or other declarative IaC tools like Terraform or Kubernetes.",{"type":21,"tag":224,"props":225,"children":227},"h3",{"id":226},"heres-how-aws-cloud-development-kit-cdk-helps-achieve-this",[228],{"type":26,"value":229},"Here's how AWS Cloud Development Kit (CDK) helps achieve this:",{"type":21,"tag":33,"props":231,"children":232},{},[233,244,263,273,283,293],{"type":21,"tag":37,"props":234,"children":235},{},[236,242],{"type":21,"tag":237,"props":238,"children":239},"strong",{},[240],{"type":26,"value":241},"Familiar Languages",{"type":26,"value":243},": Developers define cloud infrastructure using languages like TypeScript, Python, Java, C#, or Go, complete with the benefits of their existing IDEs, linters, and testing frameworks.",{"type":21,"tag":37,"props":245,"children":246},{},[247,252,254,261],{"type":21,"tag":237,"props":248,"children":249},{},[250],{"type":26,"value":251},"High-Level Constructs",{"type":26,"value":253},": CDK provides well-architected building blocks (L2/L3 constructs) like ",{"type":21,"tag":255,"props":256,"children":258},"code",{"className":257},[],[259],{"type":26,"value":260},"ApplicationLoadBalancedFargateService",{"type":26,"value":262}," which can deploy a containerized application behind a load balancer with just a few lines of code. This drastically reduces the boilerplate and complexity compared to writing raw AWS CloudFormation or Kubernetes YAML.",{"type":21,"tag":37,"props":264,"children":265},{},[266,271],{"type":21,"tag":237,"props":267,"children":268},{},[269],{"type":26,"value":270},"Reusability and Standardization",{"type":26,"value":272},": Teams can create their own custom CDK constructs that encapsulate organizational best practices for security, networking, and application deployment. For example, an \"OurStandardWebService\" construct could pre-configure logging, monitoring, and IAM permissions.",{"type":21,"tag":37,"props":274,"children":275},{},[276,281],{"type":21,"tag":237,"props":277,"children":278},{},[279],{"type":26,"value":280},"Full Programming Power",{"type":26,"value":282},": Because it's \"just code,\" you can use loops, conditionals, and software design patterns to create dynamic and sophisticated infrastructure definitions.",{"type":21,"tag":37,"props":284,"children":285},{},[286,291],{"type":21,"tag":237,"props":287,"children":288},{},[289],{"type":26,"value":290},"Synthesis to CloudFormation",{"type":26,"value":292},": Under the hood, CDK applications synthesize into AWS CloudFormation templates. This means you get the robust, declarative provisioning engine of CloudFormation, ensuring predictable and repeatable deployments, but with a far superior developer experience.",{"type":21,"tag":37,"props":294,"children":295},{},[296,301],{"type":21,"tag":237,"props":297,"children":298},{},[299],{"type":26,"value":300},"Managed Services",{"type":26,"value":302},": Committing to a certain amount of AWS platform lockin lets the team leverage all the engineering AWS puts into the platform making security, scalability and reliabilty easier to achieve at a smaller scale.",{"type":21,"tag":74,"props":304,"children":305},{},[306,319],{"type":21,"tag":224,"props":307,"children":309},{"id":308},"sidebar-industry-insights-from-thoughtworks-technology-radar-vol-32-2025",[310,312],{"type":26,"value":311},"Sidebar: Industry Insights from ",{"type":21,"tag":63,"props":313,"children":316},{"href":314,"rel":315},"https://www.thoughtworks.com/content/dam/thoughtworks/documents/radar/2025/04/tr_technology_radar_vol_32_en.pdf",[67],[317],{"type":26,"value":318},"ThoughtWorks Technology Radar Vol. 32, 2025",{"type":21,"tag":33,"props":320,"children":321},{},[322,332],{"type":21,"tag":37,"props":323,"children":324},{},[325,330],{"type":21,"tag":124,"props":326,"children":327},{},[328],{"type":26,"value":329},"Branch-per-Environment Approaches:",{"type":26,"value":331},"\n\"Branch-per-environment strategies offer teams the ability to manage complex deployments by maintaining distinct code branches for each environment, facilitating isolated testing and improved reliability.\"",{"type":21,"tag":37,"props":333,"children":334},{},[335,340],{"type":21,"tag":124,"props":336,"children":337},{},[338],{"type":26,"value":339},"Internal Developer Platforms (IDPs):",{"type":26,"value":341},"\n\"IDPs streamline developer workflows by providing centralized tooling and self-service capabilities, reducing cognitive load, and standardizing deployments.\"",{"type":21,"tag":22,"props":343,"children":344},{},[345],{"type":26,"value":346},"While this article isn't a tutorial on using AWS CDK (plenty of excellent resources exist for that, which you can explore in further reading below), the practical application of this approach transforms the development workflow. When a developer initiates a new feature by creating a Git branch, the CDK deployment (IaC) code, which lives directly alongside the application code within that same branch, is deployed. Immediately the ephemeral environment is created. This means QA, product managers, or other stakeholders can \"take a quick look\" at any point, fostering early feedback and collaboration without waiting for a developer to manually provision or deem it \"ready\" — while letting the developer validate his code in a production-like environment immediately. I advocate strongly for this approach where the environment is created and destroyed automatically. However, a more manual process where the developer decides when to create and destroy the ephemeral environment is also easy and viable with this technology.",{"type":21,"tag":224,"props":348,"children":350},{"id":349},"bundle-and-deploy-containerized-backend-quickly",[351],{"type":26,"value":352},"Bundle and Deploy Containerized Backend Quickly",{"type":21,"tag":22,"props":354,"children":355},{},[356,358,365],{"type":26,"value":357},"In CDK, a small block of python code defines, builds and deploys a containerized, load balanced web backend. This code can be in any of a ",{"type":21,"tag":63,"props":359,"children":362},{"href":360,"rel":361},"https://docs.aws.amazon.com/cdk/v2/guide/languages.html",[67],[363],{"type":26,"value":364},"variety of different languages",{"type":26,"value":366},". For our example, we'll be using Python:",{"type":21,"tag":368,"props":369,"children":372},"pre",{"className":370,"code":371,"language":14,"meta":8,"style":8},"language-python shiki shiki-themes github-light github-dark","fargate = ecs_patterns.ApplicationLoadBalancedFargateService(\n    self,\n    'apiFargate',\n    cpu=self.config.get('fargate_cpu', 256),\n    desired_count=self.config.get('fargate_desired_count', 2),\n    task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(\n        image=ecs.ContainerImage.from_asset(\n            directory=self.project_dir,                    \n        ),\n        container_port=self.config.get('fargate_container_port', 80),\n        enable_logging=self.config.get('fargate_enable_logging', True),\n        environment={\n            'FRONTEND_URL': self.url,\n            'DB_URI': f'{self.rds_url}',\n            'COMPANY_ID': '9999999999999',\n            'DB_NAME': 'tracker',\n            'QBO_CLIENT_ID': 'XXXXXXXXXXXXXXXXXXXXXXXXXX',\n            'ENVIRONMENT': 'production' if self.stage == 'production' else 'sandbox',\n            'GITLAB_URL': 'https://xxx.xxx.net',\n            'GITLAB_CLIENT_SECRET_NAME': 'gitlab-client-secret',\n            'GITLAB_AUTH_SECRET_NAME': 'gitlab-auth-secret',\n            'QBO_SECRET_NAME': 'qbo-auth-secret',\n            'KEYCLOAK_USERNAME_MIMIC': 'gitlabdev_dev',\n        },\n        secrets={\n            'DB_PASSWORD': ecs.Secret.from_secrets_manager(self.rds_secret, field='password'),\n            'DB_USERNAME': ecs.Secret.from_secrets_manager(self.rds_secret, field='username'),\n            'QBO_CLIENT_SECRET': ecs.Secret.from_secrets_manager(self.qbo_client_secret, field='client_secret'),\n        }\n    ),\n    memory_limit_mib=self.config.get('fargate_memory_limit_mib', 512),\n    public_load_balancer=self.config.get('fargate_public_load_balancer', False),\n    redirect_http=False,\n    enable_execute_command=self.config.get('fargate_enable_execute_command', False),\n    health_check_grace_period=Duration.seconds(self.config.get('fargate_health_check_grace_period', 60)),\n    max_healthy_percent=self.config.get('fargate_max_healthy_percent', 200),\n    min_healthy_percent=self.config.get('fargate_min_healthy_percent', 50),\n    vpc=self.vpc,            \n)\n",[373],{"type":21,"tag":255,"props":374,"children":375},{"__ignoreMap":8},[376,398,413,427,471,510,528,546,568,577,616,655,673,696,742,764,786,808,865,887,909,931,953,975,984,1001,1042,1080,1119,1128,1137,1176,1215,1236,1274,1319,1358,1397,1419],{"type":21,"tag":169,"props":377,"children":380},{"class":378,"line":379},"line",1,[381,387,393],{"type":21,"tag":169,"props":382,"children":384},{"style":383},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[385],{"type":26,"value":386},"fargate ",{"type":21,"tag":169,"props":388,"children":390},{"style":389},"--shiki-default:#D73A49;--shiki-dark:#F97583",[391],{"type":26,"value":392},"=",{"type":21,"tag":169,"props":394,"children":395},{"style":383},[396],{"type":26,"value":397}," ecs_patterns.ApplicationLoadBalancedFargateService(\n",{"type":21,"tag":169,"props":399,"children":401},{"class":378,"line":400},2,[402,408],{"type":21,"tag":169,"props":403,"children":405},{"style":404},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[406],{"type":26,"value":407},"    self",{"type":21,"tag":169,"props":409,"children":410},{"style":383},[411],{"type":26,"value":412},",\n",{"type":21,"tag":169,"props":414,"children":416},{"class":378,"line":415},3,[417,423],{"type":21,"tag":169,"props":418,"children":420},{"style":419},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[421],{"type":26,"value":422},"    'apiFargate'",{"type":21,"tag":169,"props":424,"children":425},{"style":383},[426],{"type":26,"value":412},{"type":21,"tag":169,"props":428,"children":430},{"class":378,"line":429},4,[431,437,441,446,451,456,461,466],{"type":21,"tag":169,"props":432,"children":434},{"style":433},"--shiki-default:#E36209;--shiki-dark:#FFAB70",[435],{"type":26,"value":436},"    cpu",{"type":21,"tag":169,"props":438,"children":439},{"style":389},[440],{"type":26,"value":392},{"type":21,"tag":169,"props":442,"children":443},{"style":404},[444],{"type":26,"value":445},"self",{"type":21,"tag":169,"props":447,"children":448},{"style":383},[449],{"type":26,"value":450},".config.get(",{"type":21,"tag":169,"props":452,"children":453},{"style":419},[454],{"type":26,"value":455},"'fargate_cpu'",{"type":21,"tag":169,"props":457,"children":458},{"style":383},[459],{"type":26,"value":460},", ",{"type":21,"tag":169,"props":462,"children":463},{"style":404},[464],{"type":26,"value":465},"256",{"type":21,"tag":169,"props":467,"children":468},{"style":383},[469],{"type":26,"value":470},"),\n",{"type":21,"tag":169,"props":472,"children":474},{"class":378,"line":473},5,[475,480,484,488,492,497,501,506],{"type":21,"tag":169,"props":476,"children":477},{"style":433},[478],{"type":26,"value":479},"    desired_count",{"type":21,"tag":169,"props":481,"children":482},{"style":389},[483],{"type":26,"value":392},{"type":21,"tag":169,"props":485,"children":486},{"style":404},[487],{"type":26,"value":445},{"type":21,"tag":169,"props":489,"children":490},{"style":383},[491],{"type":26,"value":450},{"type":21,"tag":169,"props":493,"children":494},{"style":419},[495],{"type":26,"value":496},"'fargate_desired_count'",{"type":21,"tag":169,"props":498,"children":499},{"style":383},[500],{"type":26,"value":460},{"type":21,"tag":169,"props":502,"children":503},{"style":404},[504],{"type":26,"value":505},"2",{"type":21,"tag":169,"props":507,"children":508},{"style":383},[509],{"type":26,"value":470},{"type":21,"tag":169,"props":511,"children":513},{"class":378,"line":512},6,[514,519,523],{"type":21,"tag":169,"props":515,"children":516},{"style":433},[517],{"type":26,"value":518},"    task_image_options",{"type":21,"tag":169,"props":520,"children":521},{"style":389},[522],{"type":26,"value":392},{"type":21,"tag":169,"props":524,"children":525},{"style":383},[526],{"type":26,"value":527},"ecs_patterns.ApplicationLoadBalancedTaskImageOptions(\n",{"type":21,"tag":169,"props":529,"children":531},{"class":378,"line":530},7,[532,537,541],{"type":21,"tag":169,"props":533,"children":534},{"style":433},[535],{"type":26,"value":536},"        image",{"type":21,"tag":169,"props":538,"children":539},{"style":389},[540],{"type":26,"value":392},{"type":21,"tag":169,"props":542,"children":543},{"style":383},[544],{"type":26,"value":545},"ecs.ContainerImage.from_asset(\n",{"type":21,"tag":169,"props":547,"children":549},{"class":378,"line":548},8,[550,555,559,563],{"type":21,"tag":169,"props":551,"children":552},{"style":433},[553],{"type":26,"value":554},"            directory",{"type":21,"tag":169,"props":556,"children":557},{"style":389},[558],{"type":26,"value":392},{"type":21,"tag":169,"props":560,"children":561},{"style":404},[562],{"type":26,"value":445},{"type":21,"tag":169,"props":564,"children":565},{"style":383},[566],{"type":26,"value":567},".project_dir,                    \n",{"type":21,"tag":169,"props":569,"children":571},{"class":378,"line":570},9,[572],{"type":21,"tag":169,"props":573,"children":574},{"style":383},[575],{"type":26,"value":576},"        ),\n",{"type":21,"tag":169,"props":578,"children":580},{"class":378,"line":579},10,[581,586,590,594,598,603,607,612],{"type":21,"tag":169,"props":582,"children":583},{"style":433},[584],{"type":26,"value":585},"        container_port",{"type":21,"tag":169,"props":587,"children":588},{"style":389},[589],{"type":26,"value":392},{"type":21,"tag":169,"props":591,"children":592},{"style":404},[593],{"type":26,"value":445},{"type":21,"tag":169,"props":595,"children":596},{"style":383},[597],{"type":26,"value":450},{"type":21,"tag":169,"props":599,"children":600},{"style":419},[601],{"type":26,"value":602},"'fargate_container_port'",{"type":21,"tag":169,"props":604,"children":605},{"style":383},[606],{"type":26,"value":460},{"type":21,"tag":169,"props":608,"children":609},{"style":404},[610],{"type":26,"value":611},"80",{"type":21,"tag":169,"props":613,"children":614},{"style":383},[615],{"type":26,"value":470},{"type":21,"tag":169,"props":617,"children":619},{"class":378,"line":618},11,[620,625,629,633,637,642,646,651],{"type":21,"tag":169,"props":621,"children":622},{"style":433},[623],{"type":26,"value":624},"        enable_logging",{"type":21,"tag":169,"props":626,"children":627},{"style":389},[628],{"type":26,"value":392},{"type":21,"tag":169,"props":630,"children":631},{"style":404},[632],{"type":26,"value":445},{"type":21,"tag":169,"props":634,"children":635},{"style":383},[636],{"type":26,"value":450},{"type":21,"tag":169,"props":638,"children":639},{"style":419},[640],{"type":26,"value":641},"'fargate_enable_logging'",{"type":21,"tag":169,"props":643,"children":644},{"style":383},[645],{"type":26,"value":460},{"type":21,"tag":169,"props":647,"children":648},{"style":404},[649],{"type":26,"value":650},"True",{"type":21,"tag":169,"props":652,"children":653},{"style":383},[654],{"type":26,"value":470},{"type":21,"tag":169,"props":656,"children":658},{"class":378,"line":657},12,[659,664,668],{"type":21,"tag":169,"props":660,"children":661},{"style":433},[662],{"type":26,"value":663},"        environment",{"type":21,"tag":169,"props":665,"children":666},{"style":389},[667],{"type":26,"value":392},{"type":21,"tag":169,"props":669,"children":670},{"style":383},[671],{"type":26,"value":672},"{\n",{"type":21,"tag":169,"props":674,"children":676},{"class":378,"line":675},13,[677,682,687,691],{"type":21,"tag":169,"props":678,"children":679},{"style":419},[680],{"type":26,"value":681},"            'FRONTEND_URL'",{"type":21,"tag":169,"props":683,"children":684},{"style":383},[685],{"type":26,"value":686},": ",{"type":21,"tag":169,"props":688,"children":689},{"style":404},[690],{"type":26,"value":445},{"type":21,"tag":169,"props":692,"children":693},{"style":383},[694],{"type":26,"value":695},".url,\n",{"type":21,"tag":169,"props":697,"children":699},{"class":378,"line":698},14,[700,705,709,714,719,724,729,734,738],{"type":21,"tag":169,"props":701,"children":702},{"style":419},[703],{"type":26,"value":704},"            'DB_URI'",{"type":21,"tag":169,"props":706,"children":707},{"style":383},[708],{"type":26,"value":686},{"type":21,"tag":169,"props":710,"children":711},{"style":389},[712],{"type":26,"value":713},"f",{"type":21,"tag":169,"props":715,"children":716},{"style":419},[717],{"type":26,"value":718},"'",{"type":21,"tag":169,"props":720,"children":721},{"style":404},[722],{"type":26,"value":723},"{self",{"type":21,"tag":169,"props":725,"children":726},{"style":383},[727],{"type":26,"value":728},".rds_url",{"type":21,"tag":169,"props":730,"children":731},{"style":404},[732],{"type":26,"value":733},"}",{"type":21,"tag":169,"props":735,"children":736},{"style":419},[737],{"type":26,"value":718},{"type":21,"tag":169,"props":739,"children":740},{"style":383},[741],{"type":26,"value":412},{"type":21,"tag":169,"props":743,"children":745},{"class":378,"line":744},15,[746,751,755,760],{"type":21,"tag":169,"props":747,"children":748},{"style":419},[749],{"type":26,"value":750},"            'COMPANY_ID'",{"type":21,"tag":169,"props":752,"children":753},{"style":383},[754],{"type":26,"value":686},{"type":21,"tag":169,"props":756,"children":757},{"style":419},[758],{"type":26,"value":759},"'9999999999999'",{"type":21,"tag":169,"props":761,"children":762},{"style":383},[763],{"type":26,"value":412},{"type":21,"tag":169,"props":765,"children":767},{"class":378,"line":766},16,[768,773,777,782],{"type":21,"tag":169,"props":769,"children":770},{"style":419},[771],{"type":26,"value":772},"            'DB_NAME'",{"type":21,"tag":169,"props":774,"children":775},{"style":383},[776],{"type":26,"value":686},{"type":21,"tag":169,"props":778,"children":779},{"style":419},[780],{"type":26,"value":781},"'tracker'",{"type":21,"tag":169,"props":783,"children":784},{"style":383},[785],{"type":26,"value":412},{"type":21,"tag":169,"props":787,"children":789},{"class":378,"line":788},17,[790,795,799,804],{"type":21,"tag":169,"props":791,"children":792},{"style":419},[793],{"type":26,"value":794},"            'QBO_CLIENT_ID'",{"type":21,"tag":169,"props":796,"children":797},{"style":383},[798],{"type":26,"value":686},{"type":21,"tag":169,"props":800,"children":801},{"style":419},[802],{"type":26,"value":803},"'XXXXXXXXXXXXXXXXXXXXXXXXXX'",{"type":21,"tag":169,"props":805,"children":806},{"style":383},[807],{"type":26,"value":412},{"type":21,"tag":169,"props":809,"children":811},{"class":378,"line":810},18,[812,817,821,826,831,836,841,846,851,856,861],{"type":21,"tag":169,"props":813,"children":814},{"style":419},[815],{"type":26,"value":816},"            'ENVIRONMENT'",{"type":21,"tag":169,"props":818,"children":819},{"style":383},[820],{"type":26,"value":686},{"type":21,"tag":169,"props":822,"children":823},{"style":419},[824],{"type":26,"value":825},"'production'",{"type":21,"tag":169,"props":827,"children":828},{"style":389},[829],{"type":26,"value":830}," if",{"type":21,"tag":169,"props":832,"children":833},{"style":404},[834],{"type":26,"value":835}," self",{"type":21,"tag":169,"props":837,"children":838},{"style":383},[839],{"type":26,"value":840},".stage ",{"type":21,"tag":169,"props":842,"children":843},{"style":389},[844],{"type":26,"value":845},"==",{"type":21,"tag":169,"props":847,"children":848},{"style":419},[849],{"type":26,"value":850}," 'production'",{"type":21,"tag":169,"props":852,"children":853},{"style":389},[854],{"type":26,"value":855}," else",{"type":21,"tag":169,"props":857,"children":858},{"style":419},[859],{"type":26,"value":860}," 'sandbox'",{"type":21,"tag":169,"props":862,"children":863},{"style":383},[864],{"type":26,"value":412},{"type":21,"tag":169,"props":866,"children":868},{"class":378,"line":867},19,[869,874,878,883],{"type":21,"tag":169,"props":870,"children":871},{"style":419},[872],{"type":26,"value":873},"            'GITLAB_URL'",{"type":21,"tag":169,"props":875,"children":876},{"style":383},[877],{"type":26,"value":686},{"type":21,"tag":169,"props":879,"children":880},{"style":419},[881],{"type":26,"value":882},"'https://xxx.xxx.net'",{"type":21,"tag":169,"props":884,"children":885},{"style":383},[886],{"type":26,"value":412},{"type":21,"tag":169,"props":888,"children":890},{"class":378,"line":889},20,[891,896,900,905],{"type":21,"tag":169,"props":892,"children":893},{"style":419},[894],{"type":26,"value":895},"            'GITLAB_CLIENT_SECRET_NAME'",{"type":21,"tag":169,"props":897,"children":898},{"style":383},[899],{"type":26,"value":686},{"type":21,"tag":169,"props":901,"children":902},{"style":419},[903],{"type":26,"value":904},"'gitlab-client-secret'",{"type":21,"tag":169,"props":906,"children":907},{"style":383},[908],{"type":26,"value":412},{"type":21,"tag":169,"props":910,"children":912},{"class":378,"line":911},21,[913,918,922,927],{"type":21,"tag":169,"props":914,"children":915},{"style":419},[916],{"type":26,"value":917},"            'GITLAB_AUTH_SECRET_NAME'",{"type":21,"tag":169,"props":919,"children":920},{"style":383},[921],{"type":26,"value":686},{"type":21,"tag":169,"props":923,"children":924},{"style":419},[925],{"type":26,"value":926},"'gitlab-auth-secret'",{"type":21,"tag":169,"props":928,"children":929},{"style":383},[930],{"type":26,"value":412},{"type":21,"tag":169,"props":932,"children":934},{"class":378,"line":933},22,[935,940,944,949],{"type":21,"tag":169,"props":936,"children":937},{"style":419},[938],{"type":26,"value":939},"            'QBO_SECRET_NAME'",{"type":21,"tag":169,"props":941,"children":942},{"style":383},[943],{"type":26,"value":686},{"type":21,"tag":169,"props":945,"children":946},{"style":419},[947],{"type":26,"value":948},"'qbo-auth-secret'",{"type":21,"tag":169,"props":950,"children":951},{"style":383},[952],{"type":26,"value":412},{"type":21,"tag":169,"props":954,"children":956},{"class":378,"line":955},23,[957,962,966,971],{"type":21,"tag":169,"props":958,"children":959},{"style":419},[960],{"type":26,"value":961},"            'KEYCLOAK_USERNAME_MIMIC'",{"type":21,"tag":169,"props":963,"children":964},{"style":383},[965],{"type":26,"value":686},{"type":21,"tag":169,"props":967,"children":968},{"style":419},[969],{"type":26,"value":970},"'gitlabdev_dev'",{"type":21,"tag":169,"props":972,"children":973},{"style":383},[974],{"type":26,"value":412},{"type":21,"tag":169,"props":976,"children":978},{"class":378,"line":977},24,[979],{"type":21,"tag":169,"props":980,"children":981},{"style":383},[982],{"type":26,"value":983},"        },\n",{"type":21,"tag":169,"props":985,"children":987},{"class":378,"line":986},25,[988,993,997],{"type":21,"tag":169,"props":989,"children":990},{"style":433},[991],{"type":26,"value":992},"        secrets",{"type":21,"tag":169,"props":994,"children":995},{"style":389},[996],{"type":26,"value":392},{"type":21,"tag":169,"props":998,"children":999},{"style":383},[1000],{"type":26,"value":672},{"type":21,"tag":169,"props":1002,"children":1004},{"class":378,"line":1003},26,[1005,1010,1015,1019,1024,1029,1033,1038],{"type":21,"tag":169,"props":1006,"children":1007},{"style":419},[1008],{"type":26,"value":1009},"            'DB_PASSWORD'",{"type":21,"tag":169,"props":1011,"children":1012},{"style":383},[1013],{"type":26,"value":1014},": ecs.Secret.from_secrets_manager(",{"type":21,"tag":169,"props":1016,"children":1017},{"style":404},[1018],{"type":26,"value":445},{"type":21,"tag":169,"props":1020,"children":1021},{"style":383},[1022],{"type":26,"value":1023},".rds_secret, ",{"type":21,"tag":169,"props":1025,"children":1026},{"style":433},[1027],{"type":26,"value":1028},"field",{"type":21,"tag":169,"props":1030,"children":1031},{"style":389},[1032],{"type":26,"value":392},{"type":21,"tag":169,"props":1034,"children":1035},{"style":419},[1036],{"type":26,"value":1037},"'password'",{"type":21,"tag":169,"props":1039,"children":1040},{"style":383},[1041],{"type":26,"value":470},{"type":21,"tag":169,"props":1043,"children":1045},{"class":378,"line":1044},27,[1046,1051,1055,1059,1063,1067,1071,1076],{"type":21,"tag":169,"props":1047,"children":1048},{"style":419},[1049],{"type":26,"value":1050},"            'DB_USERNAME'",{"type":21,"tag":169,"props":1052,"children":1053},{"style":383},[1054],{"type":26,"value":1014},{"type":21,"tag":169,"props":1056,"children":1057},{"style":404},[1058],{"type":26,"value":445},{"type":21,"tag":169,"props":1060,"children":1061},{"style":383},[1062],{"type":26,"value":1023},{"type":21,"tag":169,"props":1064,"children":1065},{"style":433},[1066],{"type":26,"value":1028},{"type":21,"tag":169,"props":1068,"children":1069},{"style":389},[1070],{"type":26,"value":392},{"type":21,"tag":169,"props":1072,"children":1073},{"style":419},[1074],{"type":26,"value":1075},"'username'",{"type":21,"tag":169,"props":1077,"children":1078},{"style":383},[1079],{"type":26,"value":470},{"type":21,"tag":169,"props":1081,"children":1083},{"class":378,"line":1082},28,[1084,1089,1093,1097,1102,1106,1110,1115],{"type":21,"tag":169,"props":1085,"children":1086},{"style":419},[1087],{"type":26,"value":1088},"            'QBO_CLIENT_SECRET'",{"type":21,"tag":169,"props":1090,"children":1091},{"style":383},[1092],{"type":26,"value":1014},{"type":21,"tag":169,"props":1094,"children":1095},{"style":404},[1096],{"type":26,"value":445},{"type":21,"tag":169,"props":1098,"children":1099},{"style":383},[1100],{"type":26,"value":1101},".qbo_client_secret, ",{"type":21,"tag":169,"props":1103,"children":1104},{"style":433},[1105],{"type":26,"value":1028},{"type":21,"tag":169,"props":1107,"children":1108},{"style":389},[1109],{"type":26,"value":392},{"type":21,"tag":169,"props":1111,"children":1112},{"style":419},[1113],{"type":26,"value":1114},"'client_secret'",{"type":21,"tag":169,"props":1116,"children":1117},{"style":383},[1118],{"type":26,"value":470},{"type":21,"tag":169,"props":1120,"children":1122},{"class":378,"line":1121},29,[1123],{"type":21,"tag":169,"props":1124,"children":1125},{"style":383},[1126],{"type":26,"value":1127},"        }\n",{"type":21,"tag":169,"props":1129,"children":1131},{"class":378,"line":1130},30,[1132],{"type":21,"tag":169,"props":1133,"children":1134},{"style":383},[1135],{"type":26,"value":1136},"    ),\n",{"type":21,"tag":169,"props":1138,"children":1140},{"class":378,"line":1139},31,[1141,1146,1150,1154,1158,1163,1167,1172],{"type":21,"tag":169,"props":1142,"children":1143},{"style":433},[1144],{"type":26,"value":1145},"    memory_limit_mib",{"type":21,"tag":169,"props":1147,"children":1148},{"style":389},[1149],{"type":26,"value":392},{"type":21,"tag":169,"props":1151,"children":1152},{"style":404},[1153],{"type":26,"value":445},{"type":21,"tag":169,"props":1155,"children":1156},{"style":383},[1157],{"type":26,"value":450},{"type":21,"tag":169,"props":1159,"children":1160},{"style":419},[1161],{"type":26,"value":1162},"'fargate_memory_limit_mib'",{"type":21,"tag":169,"props":1164,"children":1165},{"style":383},[1166],{"type":26,"value":460},{"type":21,"tag":169,"props":1168,"children":1169},{"style":404},[1170],{"type":26,"value":1171},"512",{"type":21,"tag":169,"props":1173,"children":1174},{"style":383},[1175],{"type":26,"value":470},{"type":21,"tag":169,"props":1177,"children":1179},{"class":378,"line":1178},32,[1180,1185,1189,1193,1197,1202,1206,1211],{"type":21,"tag":169,"props":1181,"children":1182},{"style":433},[1183],{"type":26,"value":1184},"    public_load_balancer",{"type":21,"tag":169,"props":1186,"children":1187},{"style":389},[1188],{"type":26,"value":392},{"type":21,"tag":169,"props":1190,"children":1191},{"style":404},[1192],{"type":26,"value":445},{"type":21,"tag":169,"props":1194,"children":1195},{"style":383},[1196],{"type":26,"value":450},{"type":21,"tag":169,"props":1198,"children":1199},{"style":419},[1200],{"type":26,"value":1201},"'fargate_public_load_balancer'",{"type":21,"tag":169,"props":1203,"children":1204},{"style":383},[1205],{"type":26,"value":460},{"type":21,"tag":169,"props":1207,"children":1208},{"style":404},[1209],{"type":26,"value":1210},"False",{"type":21,"tag":169,"props":1212,"children":1213},{"style":383},[1214],{"type":26,"value":470},{"type":21,"tag":169,"props":1216,"children":1218},{"class":378,"line":1217},33,[1219,1224,1228,1232],{"type":21,"tag":169,"props":1220,"children":1221},{"style":433},[1222],{"type":26,"value":1223},"    redirect_http",{"type":21,"tag":169,"props":1225,"children":1226},{"style":389},[1227],{"type":26,"value":392},{"type":21,"tag":169,"props":1229,"children":1230},{"style":404},[1231],{"type":26,"value":1210},{"type":21,"tag":169,"props":1233,"children":1234},{"style":383},[1235],{"type":26,"value":412},{"type":21,"tag":169,"props":1237,"children":1239},{"class":378,"line":1238},34,[1240,1245,1249,1253,1257,1262,1266,1270],{"type":21,"tag":169,"props":1241,"children":1242},{"style":433},[1243],{"type":26,"value":1244},"    enable_execute_command",{"type":21,"tag":169,"props":1246,"children":1247},{"style":389},[1248],{"type":26,"value":392},{"type":21,"tag":169,"props":1250,"children":1251},{"style":404},[1252],{"type":26,"value":445},{"type":21,"tag":169,"props":1254,"children":1255},{"style":383},[1256],{"type":26,"value":450},{"type":21,"tag":169,"props":1258,"children":1259},{"style":419},[1260],{"type":26,"value":1261},"'fargate_enable_execute_command'",{"type":21,"tag":169,"props":1263,"children":1264},{"style":383},[1265],{"type":26,"value":460},{"type":21,"tag":169,"props":1267,"children":1268},{"style":404},[1269],{"type":26,"value":1210},{"type":21,"tag":169,"props":1271,"children":1272},{"style":383},[1273],{"type":26,"value":470},{"type":21,"tag":169,"props":1275,"children":1277},{"class":378,"line":1276},35,[1278,1283,1287,1292,1296,1300,1305,1309,1314],{"type":21,"tag":169,"props":1279,"children":1280},{"style":433},[1281],{"type":26,"value":1282},"    health_check_grace_period",{"type":21,"tag":169,"props":1284,"children":1285},{"style":389},[1286],{"type":26,"value":392},{"type":21,"tag":169,"props":1288,"children":1289},{"style":383},[1290],{"type":26,"value":1291},"Duration.seconds(",{"type":21,"tag":169,"props":1293,"children":1294},{"style":404},[1295],{"type":26,"value":445},{"type":21,"tag":169,"props":1297,"children":1298},{"style":383},[1299],{"type":26,"value":450},{"type":21,"tag":169,"props":1301,"children":1302},{"style":419},[1303],{"type":26,"value":1304},"'fargate_health_check_grace_period'",{"type":21,"tag":169,"props":1306,"children":1307},{"style":383},[1308],{"type":26,"value":460},{"type":21,"tag":169,"props":1310,"children":1311},{"style":404},[1312],{"type":26,"value":1313},"60",{"type":21,"tag":169,"props":1315,"children":1316},{"style":383},[1317],{"type":26,"value":1318},")),\n",{"type":21,"tag":169,"props":1320,"children":1322},{"class":378,"line":1321},36,[1323,1328,1332,1336,1340,1345,1349,1354],{"type":21,"tag":169,"props":1324,"children":1325},{"style":433},[1326],{"type":26,"value":1327},"    max_healthy_percent",{"type":21,"tag":169,"props":1329,"children":1330},{"style":389},[1331],{"type":26,"value":392},{"type":21,"tag":169,"props":1333,"children":1334},{"style":404},[1335],{"type":26,"value":445},{"type":21,"tag":169,"props":1337,"children":1338},{"style":383},[1339],{"type":26,"value":450},{"type":21,"tag":169,"props":1341,"children":1342},{"style":419},[1343],{"type":26,"value":1344},"'fargate_max_healthy_percent'",{"type":21,"tag":169,"props":1346,"children":1347},{"style":383},[1348],{"type":26,"value":460},{"type":21,"tag":169,"props":1350,"children":1351},{"style":404},[1352],{"type":26,"value":1353},"200",{"type":21,"tag":169,"props":1355,"children":1356},{"style":383},[1357],{"type":26,"value":470},{"type":21,"tag":169,"props":1359,"children":1361},{"class":378,"line":1360},37,[1362,1367,1371,1375,1379,1384,1388,1393],{"type":21,"tag":169,"props":1363,"children":1364},{"style":433},[1365],{"type":26,"value":1366},"    min_healthy_percent",{"type":21,"tag":169,"props":1368,"children":1369},{"style":389},[1370],{"type":26,"value":392},{"type":21,"tag":169,"props":1372,"children":1373},{"style":404},[1374],{"type":26,"value":445},{"type":21,"tag":169,"props":1376,"children":1377},{"style":383},[1378],{"type":26,"value":450},{"type":21,"tag":169,"props":1380,"children":1381},{"style":419},[1382],{"type":26,"value":1383},"'fargate_min_healthy_percent'",{"type":21,"tag":169,"props":1385,"children":1386},{"style":383},[1387],{"type":26,"value":460},{"type":21,"tag":169,"props":1389,"children":1390},{"style":404},[1391],{"type":26,"value":1392},"50",{"type":21,"tag":169,"props":1394,"children":1395},{"style":383},[1396],{"type":26,"value":470},{"type":21,"tag":169,"props":1398,"children":1400},{"class":378,"line":1399},38,[1401,1406,1410,1414],{"type":21,"tag":169,"props":1402,"children":1403},{"style":433},[1404],{"type":26,"value":1405},"    vpc",{"type":21,"tag":169,"props":1407,"children":1408},{"style":389},[1409],{"type":26,"value":392},{"type":21,"tag":169,"props":1411,"children":1412},{"style":404},[1413],{"type":26,"value":445},{"type":21,"tag":169,"props":1415,"children":1416},{"style":383},[1417],{"type":26,"value":1418},".vpc,            \n",{"type":21,"tag":169,"props":1420,"children":1422},{"class":378,"line":1421},39,[1423],{"type":21,"tag":169,"props":1424,"children":1425},{"style":383},[1426],{"type":26,"value":1427},")\n",{"type":21,"tag":22,"props":1429,"children":1430},{},[1431],{"type":26,"value":1432},"A significant advantage emerges when infrastructure needs to evolve for a new feature: Any necessary changes to the IaC — perhaps a new database, a different type of compute resource, or adjusted network configurations — are made right there in the feature branch, directly coupled with the application code changes they support. This keeps infrastructure modifications perfectly synchronized and isolated until they are ready for review by DevOps, IT, and SecOps/DevSecOps stakeholders. Once approved, these integrated changes are pushed upstream. This principle of colocation extends to security and configuration management; by leveraging AWS IAM for precise permissions and defining infrastrucure configurations as code within the same repository (Morris 2021), the entire environment setup becomes version-controlled, transparent, and consistently reproducible. Furthermore, for robust separation, production deployments are typically targeted to an entirely separate AWS account, providing strong isolation from development and staging activities—a best practice easily facilitated by this CDK methodology.",{"type":21,"tag":22,"props":1434,"children":1435},{},[1436],{"type":26,"value":1437},"Committing to a vendor integrated technology stack like AWS and CDK allows teams to achieve a rich, deeply integrated deployment system with notably lower initial setup, ongoing maintenance, and operational costs. The inherent tradeoff is, of course, a degree of vendor lockin. However, for many organizations, particularly those operating at a scale where the agility, speed, and managed service benefits outweigh concerns about portability, this is often a pragmatic and highly effective strategic decision. Ultimately, this tight coupling of application code, IaC, configuration, and an automated environment lifecycle, all managed within the developer's familiar toolkit, is what empowers teams to build, test, and release software with greater confidence and velocity. This practice, where Git serves as the single source of truth for both application and infrastructure, and automated processes manage deployments, is a core tenet of GitOps, a key modern continuous delivery pattern.",{"type":21,"tag":74,"props":1439,"children":1440},{},[1441,1447,1452],{"type":21,"tag":224,"props":1442,"children":1444},{"id":1443},"sidebar-does-an-onprem-deployment-mean-this-wont-work-for-me",[1445],{"type":26,"value":1446},"Sidebar: Does an Onprem Deployment Mean This Won't Work for Me?",{"type":21,"tag":22,"props":1448,"children":1449},{},[1450],{"type":26,"value":1451},"This pipeline — built using all the practices discussed above — is for a project where production deployments are to a secure network environment with no connection to AWS and a requirement for manual production deployments. Despite that, the pipeline completes a complex build and test process validating the build for the unique operational and security needs of the project. Thus providing the team with reliable, on demand, ephemeral test environments and ensures that production releases are reliable and verified.",{"type":21,"tag":22,"props":1453,"children":1454},{},[1455],{"type":21,"tag":1456,"props":1457,"children":1461},"img",{"alt":1458,"src":1459,"title":1460},"Pipeline Diagram","/ewahl/2025-05/img/AwsPipeline.png","AWS Pipeline",[],{"type":21,"tag":78,"props":1463,"children":1465},{"id":1464},"addressing-common-ephemeral-environment-idp-challenges-with-a-cdk-based-approach",[1466],{"type":26,"value":1467},"Addressing Common Ephemeral Environment & IDP Challenges with a CDK-Based Approach",{"type":21,"tag":22,"props":1469,"children":1470},{},[1471,1473,1480],{"type":26,"value":1472},"While full-fledged IDPs aim to solve many operational complexities, the path to their implementation, or even to robust ephemeral environment strategies, presents significant hurdles to organizations. For instance, Qovery's article, '",{"type":21,"tag":63,"props":1474,"children":1477},{"href":1475,"rel":1476},"https://www.qovery.com/blog/challenges-to-anticipate-when-transitioning-to-an-internal-developer-platform/",[67],[1478],{"type":26,"value":1479},"Challenges to Anticipate When Transitioning to an Internal Developer Platform",{"type":26,"value":1481},"', outlines several such difficulties. A well-implemented CDK strategy for ephemeral environments, however, can addresses many of these challenges at a fraction of the cost and effort.",{"type":21,"tag":224,"props":1483,"children":1485},{"id":1484},"_1-challenge-environment-variables-and-configuration-management",[1486],{"type":26,"value":1487},"1.  Challenge: Environment Variables and Configuration Management",{"type":21,"tag":33,"props":1489,"children":1490},{},[1491,1501,1519],{"type":21,"tag":37,"props":1492,"children":1493},{},[1494,1499],{"type":21,"tag":124,"props":1495,"children":1496},{},[1497],{"type":26,"value":1498},"The Hurdle",{"type":26,"value":1500},": Managing distinct configurations, secrets, and dynamic URLs across numerous ephemeral environments can be complex.",{"type":21,"tag":37,"props":1502,"children":1503},{},[1504,1509,1511,1517],{"type":21,"tag":124,"props":1505,"children":1506},{},[1507],{"type":26,"value":1508},"CDK Mitigation",{"type":26,"value":1510},": AWS CDK can seamlessly integrate with services like AWS Systems Manager Parameter Store or AWS Secrets Manager. For each ephemeral environment, CDK code can create and populate uniquely named parameters/secrets (e.g., ",{"type":21,"tag":255,"props":1512,"children":1514},{"className":1513},[],[1515],{"type":26,"value":1516},"DATABASE_URL_feature_xyz",{"type":26,"value":1518},"). The application code within that environment is then configured to read these specific parameters. Dynamic values, like the DNS name of a load balancer created by CDK for that environment, can be directly injected as environment variables into the application's container definition or written to Parameter Store for the application to fetch.",{"type":21,"tag":37,"props":1520,"children":1521},{},[1522,1527],{"type":21,"tag":124,"props":1523,"children":1524},{},[1525],{"type":26,"value":1526},"Low Cost",{"type":26,"value":1528},": This leverages robust, built-in AWS services, managed as code within the CDK app, requiring minimal additional tooling.",{"type":21,"tag":224,"props":1530,"children":1532},{"id":1531},"_2-challenge-technical-implementation-challenges",[1533],{"type":26,"value":1534},"2.  Challenge: Technical Implementation Challenges",{"type":21,"tag":33,"props":1536,"children":1537},{},[1538,1547,1556],{"type":21,"tag":37,"props":1539,"children":1540},{},[1541,1545],{"type":21,"tag":124,"props":1542,"children":1543},{},[1544],{"type":26,"value":1498},{"type":26,"value":1546},": Setting up the initial automation for ephemeral environments can require significant effort and expertise in containerization and infrastructure tools.",{"type":21,"tag":37,"props":1548,"children":1549},{},[1550,1554],{"type":21,"tag":124,"props":1551,"children":1552},{},[1553],{"type":26,"value":1508},{"type":26,"value":1555},": CDK and its close coupling with AWS managed services significantly lowers the barrier. Instead of wrestling with verbose YAML or JSON, developers use familiar programming languages and high-level abstractions. While cloud and IaC knowledge is still needed, for a Python or TypeScript developer, learning to use CDK with the support of a DevOps team and leveraging AWS managed services often presents a gentler learning curve and operational model than becoming a Kubernetes or raw CloudFormation expert. Reusable constructs can encapsulate complex patterns, further simplifying adoption.",{"type":21,"tag":37,"props":1557,"children":1558},{},[1559,1563],{"type":21,"tag":124,"props":1560,"children":1561},{},[1562],{"type":26,"value":1526},{"type":26,"value":1564},": Capitalizes on existing developer skill sets. The need for a large, specialized platform team is reduced, especially for initial setup, as developers can own more of their infrastructure story.",{"type":21,"tag":224,"props":1566,"children":1568},{"id":1567},"_3-challenge-security-and-compliance-challenges",[1569],{"type":26,"value":1570},"3.  Challenge: Security and Compliance Challenges",{"type":21,"tag":33,"props":1572,"children":1573},{},[1574,1583,1592],{"type":21,"tag":37,"props":1575,"children":1576},{},[1577,1581],{"type":21,"tag":124,"props":1578,"children":1579},{},[1580],{"type":26,"value":1498},{"type":26,"value":1582},": Ensuring data protection, implementing role-based access control (RBAC), meeting compliance standards (GDPR, HIPAA, PCI DSS), and maintaining audit trails in transient environments.",{"type":21,"tag":37,"props":1584,"children":1585},{},[1586,1590],{"type":21,"tag":124,"props":1587,"children":1588},{},[1589],{"type":26,"value":1508},{"type":26,"value":1591},": Security is defined in code aka \"Policy as Code\". IAM roles and policies granting least-privilege access can be crafted with precision for each ephemeral stack. Security groups, network ACLs, VPC configurations, and data encryption settings (e.g., KMS keys for S3 buckets or RDS instances) are all explicitly declared in CDK are created and destroyed as needed alongside that environment. When needed, DevOps experts can step in alongside the dev team to implement appropriate security and permissions strategies, with SecOps stakeholders reviewing the results, all seamlessly before the changes get pushed upstream. This makes meeting compliance goals significantly easeir. All changes are version-controlled, and AWS CloudTrail provides an audit log of all API calls made by CDK (via CloudFormation).",{"type":21,"tag":37,"props":1593,"children":1594},{},[1595,1599],{"type":21,"tag":124,"props":1596,"children":1597},{},[1598],{"type":26,"value":1526},{"type":26,"value":1600},": Leverages AWS's robust security infrastructure and compliance certifications. \"Security as code\" makes policies transparent, reviewable, and repeatable. This 'security as code' approach embodies the shift left security principle, integrating security considerations early in the development lifecycle.",{"type":21,"tag":224,"props":1602,"children":1604},{"id":1603},"_4-challenge-resource-management",[1605],{"type":26,"value":1606},"4.  Challenge: Resource Management",{"type":21,"tag":33,"props":1608,"children":1609},{},[1610,1619,1644],{"type":21,"tag":37,"props":1611,"children":1612},{},[1613,1617],{"type":21,"tag":124,"props":1614,"children":1615},{},[1616],{"type":26,"value":1498},{"type":26,"value":1618},": Potential for resource wastage due to orphaned resources if environments aren't torn down properly, and the need to balance resource allocation.",{"type":21,"tag":37,"props":1620,"children":1621},{},[1622,1626,1628,1634,1636,1642],{"type":21,"tag":124,"props":1623,"children":1624},{},[1625],{"type":26,"value":1508},{"type":26,"value":1627},": The ",{"type":21,"tag":255,"props":1629,"children":1631},{"className":1630},[],[1632],{"type":26,"value":1633},"cdk destroy",{"type":26,"value":1635}," command is the counterpart to ",{"type":21,"tag":255,"props":1637,"children":1639},{"className":1638},[],[1640],{"type":26,"value":1641},"cdk deploy",{"type":26,"value":1643},". By integrating this command into CI/CD pipelines (e.g., on branch merge/delete), automated cleanup is enforced. CDK applications should also systematically apply tags (e.g., environment:feature-xyz, created-by:pipeline) to all resources, facilitating cost tracking and identification of any stray resources. Event Bridge and Dead Letter Queues ensure that DevOps will be notified of automation failures.",{"type":21,"tag":37,"props":1645,"children":1646},{},[1647,1651],{"type":21,"tag":124,"props":1648,"children":1649},{},[1650],{"type":26,"value":1526},{"type":26,"value":1652},": Automation is the key. The risk of manual error leading to forgotten resources is greatly diminished.",{"type":21,"tag":224,"props":1654,"children":1656},{"id":1655},"_5-challenge-team-adoption",[1657],{"type":26,"value":1658},"5.  Challenge: Team Adoption",{"type":21,"tag":33,"props":1660,"children":1661},{},[1662,1671,1680],{"type":21,"tag":37,"props":1663,"children":1664},{},[1665,1669],{"type":21,"tag":124,"props":1666,"children":1667},{},[1668],{"type":26,"value":1498},{"type":26,"value":1670},": Training teams on new ephemeral workflows and fostering a cultural shift from persistent to disposable environments.",{"type":21,"tag":37,"props":1672,"children":1673},{},[1674,1678],{"type":21,"tag":124,"props":1675,"children":1676},{},[1677],{"type":26,"value":1508},{"type":26,"value":1679},": Because CDK uses familiar programming languages, developers are often more comfortable and quicker to adopt it. They can understand, and even contribute to, the infrastructure code more readily than if it were in a DSL or complex configuration format. The \"it just works\" experience for spinning up an environment for their branch significantly smooths adoption. With DevOps experts, who are specialists in this strategy, setting up the initial project with a ready-to-use pipeline and supporting any complex infrastructure changes, the learning curve for the development team is minimal.",{"type":21,"tag":37,"props":1681,"children":1682},{},[1683,1687],{"type":21,"tag":124,"props":1684,"children":1685},{},[1686],{"type":26,"value":1526},{"type":26,"value":1688},": Reduced learning curve for developers who already code. The infrastructure becomes less of a \"black box.\"",{"type":21,"tag":224,"props":1690,"children":1692},{"id":1691},"_6-challenge-integration-complexity",[1693],{"type":26,"value":1694},"6.  Challenge: Integration Complexity",{"type":21,"tag":33,"props":1696,"children":1697},{},[1698,1707,1716],{"type":21,"tag":37,"props":1699,"children":1700},{},[1701,1705],{"type":21,"tag":124,"props":1702,"children":1703},{},[1704],{"type":26,"value":1498},{"type":26,"value":1706},": Integrating ephemeral environment provisioning into existing Continuous Integration/Continuous Delivery (CI/CD) pipelines and ensuring compatibility with development tools.",{"type":21,"tag":37,"props":1708,"children":1709},{},[1710,1714],{"type":21,"tag":124,"props":1711,"children":1712},{},[1713],{"type":26,"value":1508},{"type":26,"value":1715},": While AWS CDK integrates seamlessly with AWS native CI/CD services like CodePipeline, its command-line interface (CLI) is also designed for broader automation. This means it can be readily incorporated into established CI/CD systems your team might already use, such as GitHub Actions, GitLab CI, or Jenkins, typically by invoking shell commands. Stack outputs can then be consumed by subsequent pipeline stages, ensuring a smooth workflow regardless of your chosen CI/CD tooling.",{"type":21,"tag":37,"props":1717,"children":1718},{},[1719,1723],{"type":21,"tag":124,"props":1720,"children":1721},{},[1722],{"type":26,"value":1526},{"type":26,"value":1724},": Strong native integration with the AWS ecosystem and straightforward CLI-based integration for other systems, offering flexibility.",{"type":21,"tag":224,"props":1726,"children":1728},{"id":1727},"_7-challenge-build-vs-buy-decision-point",[1729],{"type":26,"value":1730},"7.  Challenge: Build vs. Buy Decision Point",{"type":21,"tag":33,"props":1732,"children":1733},{},[1734,1743,1752],{"type":21,"tag":37,"props":1735,"children":1736},{},[1737,1741],{"type":21,"tag":124,"props":1738,"children":1739},{},[1740],{"type":26,"value":1498},{"type":26,"value":1742},": Deciding between building a custom ephemeral environment solution (resource-intensive) or using third-party services (potential external dependencies and less customization).",{"type":21,"tag":37,"props":1744,"children":1745},{},[1746,1750],{"type":21,"tag":124,"props":1747,"children":1748},{},[1749],{"type":26,"value":1508},{"type":26,"value":1751},": CDK offers a compelling middle ground. By committing to AWS, you are essentially \"building\" your platform definition with the powerful, managed services and abstractions provided by AWS and the CDK framework. This allows you to leverage AWS's vast engineering investment directly, meaning there's significantly less undifferentiated heavy lifting for your team to fund and build from scratch when composing and configuring high-level AWS services.",{"type":21,"tag":37,"props":1753,"children":1754},{},[1755,1759],{"type":21,"tag":124,"props":1756,"children":1757},{},[1758],{"type":26,"value":1526},{"type":26,"value":1760},": You leverage AWS's massive investment in its cloud services and the open-source CDK, getting a significant head start compared to a pure custom build, while retaining more control and customization than many off-the-shelf IDP products.",{"type":21,"tag":22,"props":1762,"children":1763},{},[1764],{"type":26,"value":1765},"By thoughtfully applying CDK, many of the advantages of an IDP—developer self-service, automation, consistency, and speed—become accessible without the monumental effort of building or buying a full-scale platform from day one.",{"type":21,"tag":78,"props":1767,"children":1769},{"id":1768},"enhanced-testing-and-collaboration-with-cdk-driven-ephemeral-environments",[1770],{"type":26,"value":1771},"Enhanced Testing and Collaboration with CDK-Driven Ephemeral Environments",{"type":21,"tag":22,"props":1773,"children":1774},{},[1775],{"type":26,"value":1776},"The CDK-driven approach to ephemeral environments isn't merely a technical exercise; it delivers substantial, practical benefits that directly enhance both the quality of testing and the efficiency of team collaboration. By defining each environment as code, directly alongside the application, teams can test early, often, and with a high degree of realism.",{"type":21,"tag":22,"props":1778,"children":1779},{},[1780],{"type":26,"value":1781},"One of the most immediate advantages is the early detection of incompatibilities between the application and its underlying infrastructure. Because each feature branch can have its own distinct, CDK-defined environment, any infrastructure changes required for that feature are tested in isolation. This means issues that might arise from, say, a new database version or an altered network configuration are identified and resolved within the context of that feature's development, long before they could impact a shared staging environment or, worse, production. Furthermore, this isolation ensures that individual bugs or incomplete features on one branch don't derail the work of others. Developers can confidently experiment and iterate, knowing their environment is a safe sandbox, preventing the all-too-common scenario where one person's unstable code destabilizes a shared testing ground and blocks overall team progress. This is a practical application of shift left testing.",{"type":21,"tag":22,"props":1783,"children":1784},{},[1785],{"type":26,"value":1786},"This ability to spin up and tear down environments on demand also translates to greater agility when priorities shift. If a new, urgent task arises, a dedicated environment for it can be provisioned quickly via CDK without disrupting ongoing work or requiring complex manual reconfiguration of existing test setups. While development or feature-branch environments might sometimes run lighter configurations to optimize costs, the core software stack and its interaction with the CDK-defined infrastructure remain consistent, ensuring that testing efforts are meaningful and translate reliably to production.",{"type":21,"tag":22,"props":1788,"children":1789},{},[1790],{"type":26,"value":1791},"The power of this approach truly shines during high-pressure situations, as illustrated by a common scenario: deploying a critical hotfix. As a team lead, I vividly recall the stress of needing to push an urgent fix to production. In the past, deploying this to a shared staging environment was fraught with risk, potentially destabilizing ongoing testing for the next major release. Coordinating a \"quiet period\" with everyone anxiously watching was never pleasant. With CDK-managed ephemeral environments, the experience is entirely different. QA can spin up a pristine, production-like environment in minutes, validate the hotfix in complete isolation, and confidently give the green light, all while the main testing tracks for upcoming features remain undisturbed. This agility and safety proved invaluable for managing risk and has now become a cornerstone of our everyday development. Feature branches consistently receive isolated and thorough QA, resulting in seamless release cycles that often go unnoticed — a mark of true success.",{"type":21,"tag":22,"props":1793,"children":1794},{},[1795],{"type":26,"value":1796},"Beyond these direct testing benefits, integrating IaC code (via CDK) side-by-side with application code fosters a more collaborative and transparent development culture. When infrastructure definitions are visible and version-controlled within the same repository, developers gain a clearer understanding of the operational aspects of their features and feel more empowered to suggest improvements or identify potential issues early. DevOps and SecOps teams can more easily review infrastructure changes as part of the standard code review process, ensuring consistency, adherence to best practices, and preventing configuration drift. This shared ownership transforms infrastructure from a siloed concern into a team-driven, continuously improved asset, aligning perfectly with the DevOps philosophy.",{"type":21,"tag":78,"props":1798,"children":1800},{"id":1799},"conclusion-the-best-of-both-worlds-without-the-big-idp-bill",[1801],{"type":26,"value":1802},"Conclusion: The Best of Both Worlds Without the Big IDP Bill",{"type":21,"tag":22,"props":1804,"children":1805},{},[1806],{"type":26,"value":1807},"Adopting a strategy where Infrastructure as Code, particularly using a powerful framework like AWS CDK, lives alongside your application code isn't just a technical nicety; it's a fundamental shift towards more agile, reliable, and developer-centric software delivery. This approach allows teams to spin up and tear down fully independent, production-like ephemeral environments for every feature branch, pull request, or hotfix.",{"type":21,"tag":22,"props":1809,"children":1810},{},[1811],{"type":26,"value":1812},"The outcome is a development lifecycle supercharged with early feedback, isolated testing, and dramatically reduced integration risks. While the dream of a comprehensive Internal Developer Platform is appealing, the reality is that many of its most impactful benefits — developer self-service for environments, robust automation, enhanced collaboration, and deployment consistency — can be achieved with significantly less overhead. By empowering developers with tools like AWS CDK that leverage their existing programming skills, organizations can cultivate a \"platform thinking\" mindset and realize tangible improvements in speed and quality, without the substantial cost and complexity often associated with building a full-blown IDP.",{"type":21,"tag":78,"props":1814,"children":1816},{"id":1815},"optimizing-git-branching-strategy",[1817],{"type":26,"value":1818},"Optimizing Git Branching Strategy",{"type":21,"tag":22,"props":1820,"children":1821},{},[1822],{"type":26,"value":1823},"The moments I appreciate most about our deployment strategy aren't dramatic near-misses or tense rollbacks — it's exactly the opposite. It's when I realize I've gone a month without needing to micromanage merges or fret about someone accidentally pushing half-tested code to production. Thanks to reliable ephemeral environments defined in CDK and a clear branching policy, our team knows exactly what's expected. New features get smoothly tested, merged, and deployed without fuss. This frees me up to do the part of my job I love the most: crafting features that delight users and occasionally earn some kudos from colleagues.",{"type":21,"tag":78,"props":1825,"children":1827},{"id":1826},"next-step-with-ephemeral-deployments-and-cdk",[1828],{"type":26,"value":1829},"Next Step with Ephemeral Deployments and  CDK",{"type":21,"tag":22,"props":1831,"children":1832},{},[1833],{"type":26,"value":1834},"Here is a roadmap to implementing the practices discussed in this article:",{"type":21,"tag":1836,"props":1837,"children":1838},"table",{},[1839,1863],{"type":21,"tag":1840,"props":1841,"children":1842},"thead",{},[1843],{"type":21,"tag":1844,"props":1845,"children":1846},"tr",{},[1847,1853,1858],{"type":21,"tag":1848,"props":1849,"children":1850},"th",{},[1851],{"type":26,"value":1852},"#",{"type":21,"tag":1848,"props":1854,"children":1855},{},[1856],{"type":26,"value":1857},"Principle to Follow",{"type":21,"tag":1848,"props":1859,"children":1860},{},[1861],{"type":26,"value":1862},"Practical CDK Implementation Details",{"type":21,"tag":1864,"props":1865,"children":1866},"tbody",{},[1867,1894,1918,1951,1990,2036,2061,2086,2111],{"type":21,"tag":1844,"props":1868,"children":1869},{},[1870,1876,1884],{"type":21,"tag":1871,"props":1872,"children":1873},"td",{},[1874],{"type":26,"value":1875},"1",{"type":21,"tag":1871,"props":1877,"children":1878},{},[1879],{"type":21,"tag":237,"props":1880,"children":1881},{},[1882],{"type":26,"value":1883},"Embrace Infrastructure as Code (IaC) Fully",{"type":21,"tag":1871,"props":1885,"children":1886},{},[1887,1892],{"type":21,"tag":237,"props":1888,"children":1889},{},[1890],{"type":26,"value":1891},"CDK Action:",{"type":26,"value":1893}," Start by defining a small, manageable piece of your infrastructure (e.g., a single microservice and its database) using AWS CDK in your preferred language (TypeScript, Python, etc.). Commit this to a version control system like Git.",{"type":21,"tag":1844,"props":1895,"children":1896},{},[1897,1901,1909],{"type":21,"tag":1871,"props":1898,"children":1899},{},[1900],{"type":26,"value":505},{"type":21,"tag":1871,"props":1902,"children":1903},{},[1904],{"type":21,"tag":237,"props":1905,"children":1906},{},[1907],{"type":26,"value":1908},"Co-locate IaC with Application Code",{"type":21,"tag":1871,"props":1910,"children":1911},{},[1912,1916],{"type":21,"tag":237,"props":1913,"children":1914},{},[1915],{"type":26,"value":1891},{"type":26,"value":1917}," Ensure your CDK application code resides in the same Git repository as the application code it provisions infrastructure for. Structure your repository so that a feature branch contains both app and related infrastructure code.",{"type":21,"tag":1844,"props":1919,"children":1920},{},[1921,1926,1934],{"type":21,"tag":1871,"props":1922,"children":1923},{},[1924],{"type":26,"value":1925},"3",{"type":21,"tag":1871,"props":1927,"children":1928},{},[1929],{"type":21,"tag":237,"props":1930,"children":1931},{},[1932],{"type":26,"value":1933},"Establish an Ephemeral Environment Strategy",{"type":21,"tag":1871,"props":1935,"children":1936},{},[1937,1941,1943,1949],{"type":21,"tag":237,"props":1938,"children":1939},{},[1940],{"type":26,"value":1891},{"type":26,"value":1942}," Design your CDK stacks to be parameterizable (e.g., by branch name or feature ID). Use stack naming conventions (e.g., ",{"type":21,"tag":255,"props":1944,"children":1946},{"className":1945},[],[1947],{"type":26,"value":1948},"MyServiceStack-feature-xyz",{"type":26,"value":1950},") to ensure uniqueness for each ephemeral deployment.",{"type":21,"tag":1844,"props":1952,"children":1953},{},[1954,1959,1967],{"type":21,"tag":1871,"props":1955,"children":1956},{},[1957],{"type":26,"value":1958},"4",{"type":21,"tag":1871,"props":1960,"children":1961},{},[1962],{"type":21,"tag":237,"props":1963,"children":1964},{},[1965],{"type":26,"value":1966},"Automate Environment Provisioning & Destruction",{"type":21,"tag":1871,"props":1968,"children":1969},{},[1970,1974,1976,1981,1983,1988],{"type":21,"tag":237,"props":1971,"children":1972},{},[1973],{"type":26,"value":1891},{"type":26,"value":1975}," Integrate ",{"type":21,"tag":255,"props":1977,"children":1979},{"className":1978},[],[1980],{"type":26,"value":1641},{"type":26,"value":1982}," into your CI/CD pipeline (e.g., GitHub Actions, GitLab CI, AWS CodePipeline) to automatically create an environment when a feature branch is created/pushed. Implement ",{"type":21,"tag":255,"props":1984,"children":1986},{"className":1985},[],[1987],{"type":26,"value":1633},{"type":26,"value":1989}," in the pipeline to tear down the environment when the branch is merged/deleted.",{"type":21,"tag":1844,"props":1991,"children":1992},{},[1993,1998,2006],{"type":21,"tag":1871,"props":1994,"children":1995},{},[1996],{"type":26,"value":1997},"5",{"type":21,"tag":1871,"props":1999,"children":2000},{},[2001],{"type":21,"tag":237,"props":2002,"children":2003},{},[2004],{"type":26,"value":2005},"Leverage High-Level Constructs for Productivity",{"type":21,"tag":1871,"props":2007,"children":2008},{},[2009,2013,2015,2020,2021,2027,2028,2034],{"type":21,"tag":237,"props":2010,"children":2011},{},[2012],{"type":26,"value":1891},{"type":26,"value":2014}," Utilize CDK's L2/L3 constructs (e.g., ",{"type":21,"tag":255,"props":2016,"children":2018},{"className":2017},[],[2019],{"type":26,"value":260},{"type":26,"value":460},{"type":21,"tag":255,"props":2022,"children":2024},{"className":2023},[],[2025],{"type":26,"value":2026},"Queue",{"type":26,"value":460},{"type":21,"tag":255,"props":2029,"children":2031},{"className":2030},[],[2032],{"type":26,"value":2033},"Bucket",{"type":26,"value":2035},") to rapidly define common infrastructure patterns with minimal code, reducing boilerplate and accelerating development.",{"type":21,"tag":1844,"props":2037,"children":2038},{},[2039,2044,2052],{"type":21,"tag":1871,"props":2040,"children":2041},{},[2042],{"type":26,"value":2043},"6",{"type":21,"tag":1871,"props":2045,"children":2046},{},[2047],{"type":21,"tag":237,"props":2048,"children":2049},{},[2050],{"type":26,"value":2051},"Develop Reusable, Standardized Components",{"type":21,"tag":1871,"props":2053,"children":2054},{},[2055,2059],{"type":21,"tag":237,"props":2056,"children":2057},{},[2058],{"type":26,"value":1891},{"type":26,"value":2060}," Create your own custom L3 constructs that encapsulate your organization's best practices for common services (e.g., \"OurStandardWebService\" with pre-configured logging, security groups, IAM roles). Share these constructs across teams -- eg publish as a custom python package within your organization.",{"type":21,"tag":1844,"props":2062,"children":2063},{},[2064,2069,2077],{"type":21,"tag":1871,"props":2065,"children":2066},{},[2067],{"type":26,"value":2068},"7",{"type":21,"tag":1871,"props":2070,"children":2071},{},[2072],{"type":21,"tag":237,"props":2073,"children":2074},{},[2075],{"type":26,"value":2076},"Manage Configuration and Secrets as Code",{"type":21,"tag":1871,"props":2078,"children":2079},{},[2080,2084],{"type":21,"tag":237,"props":2081,"children":2082},{},[2083],{"type":26,"value":1891},{"type":26,"value":2085}," Integrate CDK with AWS Systems Manager Parameter Store or AWS Secrets Manager. Your CDK code should create/retrieve uniquely named parameters/secrets for each ephemeral environment and pass them to your application (e.g., via environment variables in container definitions).",{"type":21,"tag":1844,"props":2087,"children":2088},{},[2089,2094,2102],{"type":21,"tag":1871,"props":2090,"children":2091},{},[2092],{"type":26,"value":2093},"8",{"type":21,"tag":1871,"props":2095,"children":2096},{},[2097],{"type":21,"tag":237,"props":2098,"children":2099},{},[2100],{"type":26,"value":2101},"Implement Security as Code",{"type":21,"tag":1871,"props":2103,"children":2104},{},[2105,2109],{"type":21,"tag":237,"props":2106,"children":2107},{},[2108],{"type":26,"value":1891},{"type":26,"value":2110}," Define IAM roles, security groups, network ACLs, and encryption settings directly within your CDK application. Use least-privilege principles for roles associated with ephemeral resources. Leverage AWS Config or custom CDK aspects for compliance checks.",{"type":21,"tag":1844,"props":2112,"children":2113},{},[2114,2119,2127],{"type":21,"tag":1871,"props":2115,"children":2116},{},[2117],{"type":26,"value":2118},"9",{"type":21,"tag":1871,"props":2120,"children":2121},{},[2122],{"type":21,"tag":237,"props":2123,"children":2124},{},[2125],{"type":26,"value":2126},"Focus on Resource Management and Cost Control",{"type":21,"tag":1871,"props":2128,"children":2129},{},[2130,2134,2136,2142,2143,2149,2150,2156,2158,2163],{"type":21,"tag":237,"props":2131,"children":2132},{},[2133],{"type":26,"value":1891},{"type":26,"value":2135}," Systematically apply tags (e.g., ",{"type":21,"tag":255,"props":2137,"children":2139},{"className":2138},[],[2140],{"type":26,"value":2141},"environment:feature-xyz",{"type":26,"value":460},{"type":21,"tag":255,"props":2144,"children":2146},{"className":2145},[],[2147],{"type":26,"value":2148},"created-by:pipeline",{"type":26,"value":460},{"type":21,"tag":255,"props":2151,"children":2153},{"className":2152},[],[2154],{"type":26,"value":2155},"cost-center:my-team",{"type":26,"value":2157},") to all resources provisioned by CDK for tracking. Ensure automated ",{"type":21,"tag":255,"props":2159,"children":2161},{"className":2160},[],[2162],{"type":26,"value":1633},{"type":26,"value":2164}," processes are robust and have alerting for failures (e.g., using EventBridge, DLQ).",{"type":21,"tag":78,"props":2166,"children":2168},{"id":2167},"further-reading",[2169],{"type":26,"value":2170},"Further Reading",{"type":21,"tag":33,"props":2172,"children":2173},{},[2174,2186,2198,2210,2222,2234,2245,2257],{"type":21,"tag":37,"props":2175,"children":2176},{},[2177,2179,2184],{"type":26,"value":2178},"Forsgren, Nicole, et al. ",{"type":21,"tag":124,"props":2180,"children":2181},{},[2182],{"type":26,"value":2183},"Accelerate: The Science of Lean Software and DevOps",{"type":26,"value":2185},". IT Revolution, 2018.",{"type":21,"tag":37,"props":2187,"children":2188},{},[2189,2191,2196],{"type":26,"value":2190},"Beyer, Betsy, et al. ",{"type":21,"tag":124,"props":2192,"children":2193},{},[2194],{"type":26,"value":2195},"Site Reliability Engineering: How Google Runs Production Systems",{"type":26,"value":2197},". O’Reilly Media, 2016.",{"type":21,"tag":37,"props":2199,"children":2200},{},[2201,2203,2208],{"type":26,"value":2202},"Kim, Gene, et al. ",{"type":21,"tag":124,"props":2204,"children":2205},{},[2206],{"type":26,"value":2207},"The DevOps Handbook",{"type":26,"value":2209},". 2nd ed., IT Revolution, 2021.",{"type":21,"tag":37,"props":2211,"children":2212},{},[2213,2215,2220],{"type":26,"value":2214},"Morris, Kief. ",{"type":21,"tag":124,"props":2216,"children":2217},{},[2218],{"type":26,"value":2219},"Infrastructure as Code",{"type":26,"value":2221},". 2nd ed., O’Reilly Media, 2021.",{"type":21,"tag":37,"props":2223,"children":2224},{},[2225,2227,2232],{"type":26,"value":2226},"Cloud Native Computing Foundation, Technical Advisory Group App Delivery. Platform Engineering Maturity Model. Cloud Native Computing Foundation, Oct. 2023, ",{"type":21,"tag":63,"props":2228,"children":2230},{"href":161,"rel":2229},[67],[2231],{"type":26,"value":161},{"type":26,"value":2233},".",{"type":21,"tag":37,"props":2235,"children":2236},{},[2237,2239],{"type":26,"value":2238},"Thoughtworks. Technology Radar Vol. 32. Thoughtworks, Apr. 2025, ",{"type":21,"tag":63,"props":2240,"children":2243},{"href":2241,"rel":2242},"https://www.thoughtworks.com/en-us/radar",[67],[2244],{"type":26,"value":2241},{"type":21,"tag":37,"props":2246,"children":2247},{},[2248,2250,2256],{"type":26,"value":2249},"Kraja, Iris, et al. \"Multi-branch pipeline management and infrastructure deployment using AWS CDK Pipelines.\" AWS DevOps Blog, Amazon Web Services, 22 Dec. 2022, ",{"type":21,"tag":63,"props":2251,"children":2254},{"href":2252,"rel":2253},"https://aws.amazon.com/blogs/devops/multi-branch-pipeline-management-and-infrastructure-deployment-using-aws-cdk-pipelines/",[67],[2255],{"type":26,"value":2252},{"type":26,"value":2233},{"type":21,"tag":37,"props":2258,"children":2259},{},[2260,2262,2268],{"type":26,"value":2261},"AWS Community Builders. \"DevSecOps with AWS- Ephemeral Environments – Creating test Environments On-Demand - Part 1.\" DEV Community, 15 Oct. 2023, ",{"type":21,"tag":63,"props":2263,"children":2266},{"href":2264,"rel":2265},"https://dev.to/aws-builders/devsecops-with-aws-ephemeral-environments-creating-test-environments-on-demand-part-1-54il",[67],[2267],{"type":26,"value":2264},{"type":26,"value":2233},{"type":21,"tag":2270,"props":2271,"children":2272},"style",{},[2273],{"type":26,"value":2274},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":415,"depth":415,"links":2276},[2277,2278,2279,2280,2287,2296,2297,2298,2299,2300],{"id":80,"depth":400,"text":83},{"id":109,"depth":400,"text":112},{"id":150,"depth":400,"text":153},{"id":187,"depth":400,"text":190,"children":2281},[2282,2283,2285,2286],{"id":226,"depth":415,"text":229},{"id":308,"depth":415,"text":2284},"Sidebar: Industry Insights from ThoughtWorks Technology Radar Vol. 32, 2025",{"id":349,"depth":415,"text":352},{"id":1443,"depth":415,"text":1446},{"id":1464,"depth":400,"text":1467,"children":2288},[2289,2290,2291,2292,2293,2294,2295],{"id":1484,"depth":415,"text":1487},{"id":1531,"depth":415,"text":1534},{"id":1567,"depth":415,"text":1570},{"id":1603,"depth":415,"text":1606},{"id":1655,"depth":415,"text":1658},{"id":1691,"depth":415,"text":1694},{"id":1727,"depth":415,"text":1730},{"id":1768,"depth":400,"text":1771},{"id":1799,"depth":400,"text":1802},{"id":1815,"depth":400,"text":1818},{"id":1826,"depth":400,"text":1829},{"id":2167,"depth":400,"text":2170},"markdown","content:ewahl:2025-05:escape_deployment_hell.md","content","ewahl/2025-05/escape_deployment_hell.md","ewahl/2025-05/escape_deployment_hell","md",{"user":2308,"name":2309},"ewahl","Edward F. Wahl",[2311,2324,2333,2346,2361,2373,2381,2391,2402,2411,2423,2434,2445,2454,2464,2475,2488,2499,2508,2518,2526,2533,2542,2550,2559,2569,2579,2588,2597,2605,2616,2624,2632,2641,2649,2658,2666,2674,2682,2690,2698,2706,2714,2724,2732,2742,2750,2760,2770,2781,2791,2801,2812,2822,2825,2834,2844,2855,2865,2877,2889,2903,2917,2932,2940,2948,2957,2966,2975,2984,2993,3002,3011,3020,3029,3040,3053,3063,3071,3079,3087,3095,3107,3119,3129,3138,3147,3157,3166,3176,3185],{"_path":2312,"title":2313,"description":2314,"image":2315,"publishDate":2316,"tags":2317,"_id":2320,"author":2321},"/alalande/2023-1/escaping_text","Thinking Like a Programmer: Escaping Text","In this progressive and vivid explanation, we explore the HOW and WHY of quoting, nesting, and escaping text.","/alalande/2023-1/img/header.png","2024-02-15",[2318,2319],"series","educational","content:alalande:2023-1:escaping_text.md",{"user":2322,"name":2323},"alalande","Anthony Lalande",{"_path":2325,"title":2326,"description":2327,"image":2328,"publishDate":2329,"tags":2330,"_id":2331,"author":2332},"/alalande/2023-2/heuristics","Thinking Like a Programmer: Heuristics","We want to help you turbo-charge your decision making.","/alalande/2023-2/img/heuristics.png","2024-03-15",[2318,2319],"content:alalande:2023-2:heuristics.md",{"user":2322,"name":2323},{"_path":2334,"title":2335,"description":2336,"tags":2337,"publishDate":2340,"image":2341,"_id":2342,"author":2343},"/areichert/2023-10/rapid-testing-techniques-for-web-and-mobile","Rapid Testing Techniques for Web & Mobile Apps","Testing rapidly or the need to test quickly under shortened execution cycles is not new. Rapid testing has been around for as long as software testing has existed but increases when a product release is imminent, strained, or behind schedule. There are many reasons development gets behind schedule. Similar to testing, development is prone to surprise requirements changes and increases in scope and complexity.",[2338,2339],"software-testing","qa","2024-06-01","/areichert/2023-10/img/rapid_testing.png","content:areichert:2023-10:Rapid Testing techniques for web and mobile.md",{"user":2344,"name":2345},"areichert","Amy Reichert",{"_path":2347,"title":2348,"description":2349,"image":2350,"publishDate":2351,"tags":2352,"_id":2357,"author":2358},"/asherbrooke/2020-4/watchwah","Bringing an Idea to Life: WatchWah Proof of Concept","\"Wouldn't it be cool if...\"","/asherbrooke/2020-4/img/Guitar_and_Watch.jpeg","2020-04-01",[2353,2354,2355,2356],"ios","apple","watch","juce","content:asherbrooke:2020-4:watchwah.md",{"user":2359,"name":2360},"asherbrooke","Andrew Sherbrooke",{"_path":2362,"title":2363,"description":2364,"publishDate":2365,"tags":2366,"_id":2369,"author":2370},"/avogan/2012-07/salt","What Your Users Don't Know (Part 1)","What's wrong with this code?","2012-07-13",[2367,2368,2318],"cryptography","security","content:avogan:2012-07:salt.md",{"user":2371,"name":2372},"avogan","Andrew Vogan",{"_path":2374,"title":2375,"description":2376,"publishDate":2377,"tags":2378,"_id":2379,"author":2380},"/avogan/2012-07/salt-2","What Your Users Don't Know (Part 2)","In my last post we saw that what your users don't know can hurt them. In other words, how securely you handle your users' private data behind the scenes can have profound implications both for your business and your users' well being. To put it bluntly, it's bad for your business to be publicly shamed over your handling of sensitive data, and it's bad for your users to have their bank accounts pilfered -- those being some of the worse case scenarios.","2012-07-26",[2367,2368,2318],"content:avogan:2012-07:salt-2.md",{"user":2371,"name":2372},{"_path":2382,"title":2383,"description":2384,"publishDate":2385,"image":2386,"_id":2387,"author":2388},"/bporter/2012-5/cd_player","My First CD Player","I started college right about the time when the first CD players were coming onto the market -- there weren't many available, and they were all obscenely expensive. At the time, my dad was dong a lot of traveling to Japan for business, and he was able to bring me a really nice Yamaha CD player back from a shop in Akihabara for about 1/4th of what a similar unit would have cost me here in the US.","2012-05-01","/bporter/2012-5/img/cd_player.jpg","content:bporter:2012-5:cd_player.md",{"user":2389,"name":2390},"bporter","Brett Porter",{"_path":2392,"title":2393,"description":2394,"publishDate":2395,"tags":2396,"image":2399,"_id":2400,"author":2401},"/bporter/2012-5/improtech","ImproTech Paris-New York 2012","Last week, I took a vacation day to attend one day of workshops at NYU as part of ImproTech 2012 Paris-New York That website descibes the event as:","2012-05-24",[2397,2398],"improvisation","music","/bporter/2012-5/img/affichemartin.jpg","content:bporter:2012-5:improtech.md",{"user":2389,"name":2390},{"_path":2403,"title":2404,"description":2405,"publishDate":2406,"tags":2407,"_id":2409,"author":2410},"/bporter/2012-5/learntocode","Yes, Do Learn To Code!","My usual pre-work routine is to walk the dog (working at home, this is my counterpart to a commute), pour my first cup of coffee, and then curl up for a little while with Google Reader. I don't know if it's because I've selected feeds that are too closely aligned with my values and personal agenda, but it's really rare that I'll read a post that is just so wrong that it makes me angry. Jeff Atwood wrote a post like that: Please Don't Learn To Code","2012-05-15",[2408],"learn-to-code","content:bporter:2012-5:learntocode.md",{"user":2389,"name":2390},{"_path":2412,"title":2413,"description":2414,"publishDate":2415,"tags":2416,"image":2420,"_id":2421,"author":2422},"/bporter/2012-6/dsl","Watch Your Language","Interesting to see a theme emerge in my Pinboard account this week -- lots of stuff about the idea of 'programming language'. I've spent the last few weeks preparing to dive back into a personal interactive music project that I've been working on sporadically since I was in graduate school. I had recently realized that the conceptual roadblock I hit before my last hiatus was something that I'd need to address by adding some sort of little programming language into the system. After following Martin Fowler's many blog posts over the years discussing domain specific languages, I finally broke down and bought his book on the topic. It's too early yet for me to have much concrete to say about the book, but I remember getting enough out of those blog posts to be confident that it will be worth the money and time to read.","2012-06-29",[2417,2418,2419],"dsl","erlang","go","/bporter/2012-6/img/dsl.jpg","content:bporter:2012-6:dsl.md",{"user":2389,"name":2390},{"_path":2424,"title":2425,"description":2426,"image":2427,"publishDate":2428,"tags":2429,"_id":2432,"author":2433},"/bporter/2019-3/animator","Friz: A flexible animation controller for JUCE","As is often the case, I found myself working on a personal project and had some UI elements that really wanted to have some life to them on the screen.","/bporter/2019-3/img/animator.png","2019-03-01",[2356,2430,2431],"ui","c++","content:bporter:2019-3:animator.md",{"user":2389,"name":2390},{"_path":2435,"title":2436,"description":2437,"image":2438,"publishDate":2439,"tags":2440,"_id":2443,"author":2444},"/bporter/2019-4/aesannounce","Art+Logic In the Real World","There are a few events coming up in the next few weeks where A+L will have people in attendance. If you're going to be there or nearby, please get in touch and we'll meet up.","/bporter/2019-4/img/aesLogo.jpg","2019-04-01",[2441,2442],"a+l","event","content:bporter:2019-4:aesAnnounce.md",{"user":2389,"name":2390},{"_path":2446,"title":2447,"description":2448,"image":2449,"publishDate":2450,"tags":2451,"_id":2452,"author":2453},"/bporter/2020-10/reanimated","Re-animated","Last year, I posted here about an animation control framework called 'Friz' that works within the JUCE Application Framework.","/bporter/2020-10/img/module.png","2020-10-01",[2356,2430,2431],"content:bporter:2020-10:reanimated.md",{"user":2389,"name":2390},{"_path":2455,"title":2456,"description":2457,"image":2458,"publishDate":2428,"tags":2459,"_id":2460,"author":2461},"/bstevens/2019-3","Coding the Impossible","As you can see on the Art+Logic website, our slogan is Coding the \"impossible.\"®","/bstevens/2019-3/img/impossible.png",[2441],"content:bstevens:2019-3:index.md",{"user":2462,"name":2463},"bstevens","Ben Stevens",{"_path":2465,"title":2466,"description":2467,"publishDate":2468,"tags":2469,"_id":2471,"author":2472},"/ckeefer/2013-1/misc","JS Hints & Shortcuts","During the course of any complex project (and even many simple ones), on the way to accomplish the actual goal, you're certain to encounter any number of small hurdles along the way - little problems which need to be resolved for the bigger picture to come into focus.","2013-09-01",[2470],"js","content:ckeefer:2013-1:misc.md",{"user":2473,"name":2474},"ckeefer","Christopher Keefer",{"_path":2476,"title":2477,"description":2478,"publishDate":2479,"tags":2480,"image":2485,"_id":2486,"author":2487},"/ckeefer/2013-2/xslt","XML and XSLT","Not terribly long ago, XML was the darling of the web. HTML4 was reformulated as XHTML 1.0, SOAP messages were XML, and let us not forget XMLHttpRequest.","2013-10-08",[2481,2482,2483,2484],"data-formats","xml","xsl","xslt","/ckeefer/2013-2/img/xslt-processing.png","content:ckeefer:2013-2:xslt.md",{"user":2473,"name":2474},{"_path":2489,"title":2490,"description":2491,"publishDate":2492,"tags":2493,"image":2496,"_id":2497,"author":2498},"/ckeefer/2013-3/ajax-upload","Ajax Upload Part I: Framed (and jQuery Deferred)","Inevitably, people want their files on the Internet. If your project is about cute cats, someone will task you with allowing users to upload photos of their cats, videos of their cats, long rambling audio clips in which they attempt to convince their cat to stop attacking the microphone, etcetera. If your project is about the nature and proclivities of mold, someone, somewhere will want to share detailed photographic evidence of their mold problem. The need to upload files is a given.","2013-03-20",[2470,2494,2495],"jquery","html5","/ckeefer/2013-3/img/upframe.jpg","content:ckeefer:2013-3:ajax-upload.md",{"user":2473,"name":2474},{"_path":2500,"title":2501,"description":2502,"publishDate":2503,"tags":2504,"_id":2506,"author":2507},"/ckeefer/2013-4/teaching-programming","Can (and Should) Everyone Learn to Program?","Fair warning: The following article is long, rambly, and contains no code. It does, however, contain some rumination on the idea that everyone can and should learn to program.","2013-12-03",[2505],"programming","content:ckeefer:2013-4:teaching-programming.md",{"user":2473,"name":2474},{"_path":2509,"title":2510,"description":2511,"publishDate":2512,"tags":2513,"image":2515,"_id":2516,"author":2517},"/ckeefer/2013-5/ajax-uploader","Ajax Upload XHR2, Take 2","It's a pleasure to be able to interact with files in the browser at long last, isn't it? Reading files in without needing to bounce them against the server first opens up a lot of possibilities - and getting progress from a chunked ajax upload is miles away from the indeterminate form uploads of days past.","2014-02-19",[2494,2470,2514],"xhr2","/ckeefer/2013-5/img/html5.jpg","content:ckeefer:2013-5:ajax-uploader.md",{"user":2473,"name":2474},{"_path":2519,"title":2520,"description":2521,"publishDate":2522,"tags":2523,"_id":2524,"author":2525},"/ckeefer/2013-07/anchors-hash","Anchors, Hash Sign, javascript:void(0)","So, you've got a link that, in reality, is just a click target for performing some javascript function. You want the appearance of a standard anchor link, but if it's not performing the intended function, should it really be an anchor? And if so, what should we fill that 'href' attribute in with?","2013-07-29",[2470],"content:ckeefer:2013-07:anchors-hash.md",{"user":2473,"name":2474},{"_path":2527,"title":2528,"description":2529,"publishDate":2530,"_id":2531,"author":2532},"/ckeefer/2013-07/static-vmware-host","Static Hosting with VMWare","Virtualization is one of the many benefits of the excess (metaphorical) horsepower available to us with modern hardware. Need to test against (Windows XP/7/8/NT || Fedora || Mint || Ubuntu || FreeBSD || MacOSX || etc)? Fire up the VM. Need a Linux environment for the packages your server relies on, but need to test in the iPad simulator? VM's to the rescue.","2013-07-26","content:ckeefer:2013-07:static-vmware-host.md",{"user":2473,"name":2474},{"_path":2534,"title":2535,"description":2536,"publishDate":2537,"tags":2538,"_id":2540,"author":2541},"/ckeefer/2013-08/fullproof-fulltext-search","Client-side Fulltext Searching with Fullproof","Recently, I was engaged in a genial argument with a friend of an older generation, each of us taking an opposing stance on some obscure trivia neither of us was entirely certain about - but which we were both ready to defend with all the wit and rhetoric at our disposal. When we had finally exhausted all attempts to make the other budge on the matter, we turned to an authoritative 3rd-party source to lay the matter to rest for us - a Google search.","2013-08-29",[2539,2484],"search","content:ckeefer:2013-08:fullproof-fulltext-search.md",{"user":2473,"name":2474},{"_path":2543,"title":2544,"description":2545,"publishDate":2546,"tags":2547,"_id":2548,"author":2549},"/ckeefer/2013-11/jquery-ajax-blobs","jQuery Ajax Blobs and Array Buffers","A big part of what makes jQuery a regular part of so many web projects is the clean interface it offers us for a number of sometimes messy built-in aspects of javascript. The most obvious is the DOM interface; and in second place, jquery ajax and its various shorthand methods. Abstracting away the difference between ActiveXObject and XMLHttpRequest is one of the most obvious benefits - but even if you don't need to worry about supporting old versions of IE, you might well enjoy the clean, object-based, promise-returning interface that jquery ajax offers.","2013-11-21",[2470,2494],"content:ckeefer:2013-11:jquery-ajax-blobs.md",{"user":2473,"name":2474},{"_path":2551,"title":2552,"description":2553,"publishDate":2554,"tags":2555,"_id":2557,"author":2558},"/ckeefer/2013-12/deploying-with-git","Deploying Websites with Git","Deploying your webapp is an important part of the web development equation - your client's site isn't going to attract a lot of attention sitting in your local dev directory. Deployment concerns tend to fall to the bottom of the priority list, though, and the end result tends to be kludgy, hastily thrown-together deployment scripts; and because they are so kludgy and, often, time consuming, when time crunches threaten, a developer may resort to making changes directly on the remote server that need to be (but sometimes never are) backported to the code living in your version control.","2013-12-23",[2556],"git","content:ckeefer:2013-12:deploying-with-git.md",{"user":2473,"name":2474},{"_path":2560,"title":2561,"description":2562,"publishDate":2563,"tags":2564,"image":2566,"_id":2567,"author":2568},"/ckeefer/2014-1/still-using-php","Still Using PHP?","Poor PHP. It's so lonely and unloved these days.","2014-01-29",[2565],"php","/ckeefer/2014-1/img/php.jpg","content:ckeefer:2014-1:still-using-php.md",{"user":2473,"name":2474},{"_path":2570,"title":2571,"description":2572,"publishDate":2573,"tags":2574,"image":2576,"_id":2577,"author":2578},"/ckeefer/2014-2/ajax-upload-2","Ajax Upload Part II: XHR2 (and FileReader)","So, the client has told you their users should be able to upload their drunken party pictures for all the internet to see. \"We want the very best experience possible,\" they tell you. \"Simple, seamless - maybe using that new html5 thing I've heard so much about.\"","2013-04-09",[2470,2575],"xmlhttprequest","/ckeefer/2014-2/img/html5.jpg","content:ckeefer:2014-2:ajax-upload-2.md",{"user":2473,"name":2474},{"_path":2580,"title":2581,"description":2582,"publishDate":2583,"tags":2584,"_id":2586,"author":2587},"/ckeefer/2014-3/customgmapsinfowindow","Custom Google Maps Info Windows","When it comes time to relate the ephemeral world of data to the physical world, Maps are key in both enterprise and consumer applications. Whatever else you might think of it, Google Maps tends to be the default option - certainly, its the only one I've ever had clients ask for by name.","2014-02-26",[2470,2585],"google-maps","content:ckeefer:2014-3:customgmapsinfowindow.md",{"user":2473,"name":2474},{"_path":2589,"title":2590,"description":2591,"publishDate":2592,"tags":2593,"_id":2595,"author":2596},"/ckeefer/2014-4/hidden-options","Hidden Options: A Workaround","Here's the situation: You've got a select. Maybe a whole bunch of selects, with a ton of options each (metric ton - let's keep our imaginary hyperbolic units straight here); and these are meant to be complex interactive elements, with options made visible or not as some programmatic condition dictates.","2014-04-23",[2594,2470,2494],"how-to","content:ckeefer:2014-4:hidden-options.md",{"user":2473,"name":2474},{"_path":2598,"title":2599,"description":2600,"publishDate":2601,"tags":2602,"_id":2603,"author":2604},"/ckeefer/2014-5/cgwin2","Custom Google Info Windows: Updated, Live","April 30, 2014 at 3:22 am Remy says:","2014-05-09",[2470,2585],"content:ckeefer:2014-5:cgwin2.md",{"user":2473,"name":2474},{"_path":2606,"title":2607,"description":2608,"publishDate":2609,"tags":2610,"image":2613,"_id":2614,"author":2615},"/ckeefer/2014-6/backbonesocketsync","Websockets for Backbone","Backbone's had some of its thunder stolen lately by trendier frameworks like Meteor and Angular; for good reason, in most cases, as without the prosthetic functionality offered by the likes of Marionette, Backbone's view handling (amongst a few other lacks and warts) is really just 'roughed in'.","2014-06-25",[2470,2611,2612],"websockets","backbone","/ckeefer/2014-6/img/WebsocketsPlusBackbone.png","content:ckeefer:2014-6:backbonesocketsync.md",{"user":2473,"name":2474},{"_path":2617,"title":2618,"description":2619,"publishDate":2620,"tags":2621,"_id":2622,"author":2623},"/ckeefer/2014-7/promises","It's a (jQuery-style) Promise","Way back when I brought up the topic of promises (particularly, jQuery Deferred), and I promised we would come back to the topic someday.","2014-10-16",[2470,2494],"content:ckeefer:2014-7:promises.md",{"user":2473,"name":2474},{"_path":2625,"title":2626,"description":2627,"publishDate":2628,"tags":2629,"_id":2630,"author":2631},"/ckeefer/2014-8/behold-views","Behold! (JavaScript Views)","JavaScript has the propensity to be very untidy - if you let it, it will sprawl all over the place. Hundreds of global variables scattered across dozens of files, messy half-measures towards object-orientation, mixed in seemingly at random with ungrouped functions - anyone who's had a client bring them a failed project from some other development team knows just how bad it can get.","2015-01-07",[2470],"content:ckeefer:2014-8:behold-views.md",{"user":2473,"name":2474},{"_path":2633,"title":2634,"description":2635,"publishDate":2636,"tags":2637,"image":2638,"_id":2639,"author":2640},"/ckeefer/2015-1/writeonce","Write Once, Debug Everywhere","It's pretty seldom that anyone mentions web pages these days, other than in historical reference to days long gone by (yes, a whole few years ago). Web sites, sure, but not if what is really wanted is to replace something that, not so long ago, would have been some native code for a smartphone (or a little further back still, a desktop computer). Generally speaking, the most common term tripping from client's lips these days is 'web applications' - or webapps, because who has time for spaces and proper spelling, amirite?","2015-02-02",[2470],"/ckeefer/2015-1/img/html5java.jpg","content:ckeefer:2015-1:writeonce.md",{"user":2473,"name":2474},{"_path":2642,"title":2643,"description":2644,"publishDate":2645,"tags":2646,"_id":2647,"author":2648},"/ckeefer/2015-2/js-frameworks","The What and Why of Javascript Frameworks","JavaScript has the propensity to be very untidy if you let it be. This isn't a problem unique to JavaScript, of course - many other languages suffer from a lack of native organization, especially for specific tasks.","2015-05-29",[2470],"content:ckeefer:2015-2:js-frameworks.md",{"user":2473,"name":2474},{"_path":2650,"title":2651,"description":2652,"publishDate":2653,"tags":2654,"_id":2656,"author":2657},"/ckeefer/2015-3/emailvalidation","Email Validation with Django and python-social-auth","When it comes to user accounts, the standard litmus test is email validation. Besides the immediate benefits - of offering us a straightforward unique identifier for users, and making it more difficult to automate creating a mass of accounts on our service - by requiring that each account have an email address and interact therewith to confirm the addresses validity, it also offers us the chance to associate a known-working email account with a user account. This is important for transactional emails such as password resets or for potential two-factor authentication use... and if you're a little less ethical, for sending marketing desirable and informative emails about interesting products and services.","2015-07-23",[14,2655],"django","content:ckeefer:2015-3:EmailValidation.md",{"user":2473,"name":2474},{"_path":2659,"title":2660,"description":2661,"publishDate":2662,"tags":2663,"_id":2664,"author":2665},"/ckeefer/2015-5/file-reader-chunking","FileReader Chunking and Base64 DataURLs","In a hurry? You can now use our HUp jquery plugin to read files in a chunked fashion as data URLs. Hooray!","2015-12-15",[2470,2494],"content:ckeefer:2015-5:file-reader-chunking.md",{"user":2473,"name":2474},{"_path":2667,"title":2668,"description":2669,"publishDate":2670,"tags":2671,"_id":2672,"author":2673},"/ckeefer/2016-1/ajaxbinarycaching","Caching Binary Data With jQuery Ajax and IndexedDB","After long, grueling months (years? or does it only feel like years?), your web application nears completion. It is tightly coded, well documented, works across all modern browsers, and is well received by your beta testers. It's nearly time to go live, and a smile of pure relief plays upon your lips... and freezes into a rictus grin when your client turns to you, and asks, \"so, hey, can we speed up the dynamic cat pic loading? Especially when I close the browser and come back to it later. I think that's really key to the whole application.\"","2016-04-25",[2470,2494],"content:ckeefer:2016-1:ajaxBinaryCaching.md",{"user":2473,"name":2474},{"_path":2675,"title":2676,"description":2677,"publishDate":2678,"tags":2679,"_id":2680,"author":2681},"/ckeefer/2016-2/paymentprocessing","Payment Processing with Braintree","You've built the web application of the century, and the users have rightly flooded to it. Cat pictures for everyone!","2016-05-11",[2655,2470,2494,14],"content:ckeefer:2016-2:paymentprocessing.md",{"user":2473,"name":2474},{"_path":2683,"title":2684,"description":2685,"publishDate":2686,"tags":2687,"_id":2688,"author":2689},"/ckeefer/2016-3/djangochannels1","Django Channels: From the Ground Up - Part 1","You stare mournfully into the mass of code you've inherited. At some point, it's clear, the requirements called for the server to push information to the client, because there's an unholy mix of Server-Side Events, long-polling, hidden iframes and even a Java applet in there, all supporting some level of long-term connectivity with the server. It's almost fascinating in its barely functional hideousness, and you would be inclined to leave well enough alone... except for the new feature specifications you've been assigned, which require the client to be able to send data back to the server in response to the received events, in as close to real-time as you can get.","2016-06-13",[2655,14,2611],"content:ckeefer:2016-3:djangoChannels1.md",{"user":2473,"name":2474},{"_path":2691,"title":2692,"description":2693,"publishDate":2694,"tags":2695,"_id":2696,"author":2697},"/ckeefer/2016-4/djangochannels2","Django Channels: From the Ground Up - Part 2","Last time, we decided to embark on a brave new adventure and give our Django framework a big upgrade with the inclusion of Django Channels. We got just far enough to get the development server running, but while this may be an adequate start, it's better to develop against something like what we intend to deploy, right?","2016-06-15",[2655,14,2611],"content:ckeefer:2016-4:djangochannels2.md",{"user":2473,"name":2474},{"_path":2699,"title":2700,"description":2701,"publishDate":2702,"tags":2703,"_id":2704,"author":2705},"/ckeefer/2016-6/gofetch1","Go Fetch! (JavaScript Fetch API)","Long ago, we briefly brushed upon the topic of what has made jQuery such a valuable part of the web developer's toolset for such a long time - namely, a cleaner interface for interacting with the DOM, and the $.ajax abstraction over XMLHttpRequest.","2016-10-03",[2470,2494,2318],"content:ckeefer:2016-6:goFetch1.md",{"user":2473,"name":2474},{"_path":2707,"title":2708,"description":2709,"publishDate":2710,"tags":2711,"_id":2712,"author":2713},"/ckeefer/2016-7/gofetch2","Go Fetch 2! (JavaScript Fetch API)","Last time we discussed the Fetch API in general, taking a look at how it differed from the XMLHttpRequest API, and some of its advantages. Today, we're going to take a look at a little library that you can include in your projects today that offers you localStorage caching for the Fetch API.","2016-10-10",[2470,2494,2318],"content:ckeefer:2016-7:goFetch2.md",{"user":2473,"name":2474},{"_path":2715,"title":2716,"description":2717,"publishDate":2718,"tags":2719,"_id":2722,"author":2723},"/ckeefer/2016-8/herokupdf","Generating PDFs: wkhtmltopdf & Heroku","So, it has come to this.","2016-12-21",[2720,2721,14],"heroku","pdf","content:ckeefer:2016-8:HerokuPDF.md",{"user":2473,"name":2474},{"_path":2725,"title":2726,"description":2727,"publishDate":2728,"tags":2729,"_id":2730,"author":2731},"/ckeefer/2017-1/downloadingclientsidecontent","Downloading Client-side Generated Content","A young developer, new to the Tao of the client-side, comes to a Master of the way, and speaks thusly: \"Oh Master, our application nears completion; and lo, cat pics can be drawn upon, and captions fixated thereto, for the creation of humour and the bounteous enjoyment of our users.\"","2017-02-06",[2470],"content:ckeefer:2017-1:downloadingclientsidecontent.md",{"user":2473,"name":2474},{"_path":2733,"title":2734,"description":2735,"publishDate":2736,"tags":2737,"_id":2740,"author":2741},"/ckeefer/2017-2/morepwatoya-part1","More PWA to Ya! (Progressive Web Apps, Part 1)","It's project kickoff time, and you're having a conversation with your client about what form the application will take:","2017-02-01",[2738,2739,2318],"pwa","mobile","content:ckeefer:2017-2:MorePWAToYa-Part1.md",{"user":2473,"name":2474},{"_path":2743,"title":2744,"description":2745,"tags":2746,"publishDate":2747,"_id":2748,"author":2749},"/ckeefer/2017-3/morepwatoya-part2","More PWA to Ya! (Progressive Web Apps, Part 2)","Last time, we got into the nitty gritty on how to make your web application into a Progressive Web Application (PWA to it's friends). I promised we'd dig even deeper this time, and show you how to make your web app a little more 'native' on Android - and how to deal with iOS Safari's special snowflake syndrome.",[2739,2738,2318],"2017-03-01","content:ckeefer:2017-3:MorePWAToYa-Part2.md",{"user":2473,"name":2474},{"_path":2751,"title":2752,"description":2753,"image":2754,"publishDate":2755,"tags":2756,"_id":2758,"author":2759},"/ckeefer/2019-1/unlockingwebaudio","Unlocking Web Audio","\"It's going to be the coolest thing ever.\"","/ckeefer/2019-1/img/featured_image.jpg","2019-01-01",[2470,2757],"audio","content:ckeefer:2019-1:UnlockingWebAudio.md",{"user":2473,"name":2474},{"_path":2761,"title":2762,"description":2763,"publishDate":2764,"image":2765,"tags":2766,"_id":2768,"author":2769},"/ckeefer/2020-1/why-vue","Why Vue","Why choose Vue over any other front-end framework?","2020-01-01","/ckeefer/2020-1/img/vue-wall.jpg",[2470,2767],"vue","content:ckeefer:2020-1:Why Vue.md",{"user":2473,"name":2474},{"_path":2771,"title":2772,"description":2773,"tags":2774,"image":2777,"publishDate":2778,"_id":2779,"author":2780},"/ckeefer/2024-3/e2e_testing","E2E Testing: To What End?","Friend, can we agree that tests are a good idea? I won't scorn you for sometimes omitting them - time and budget constraints are what they are, and even the best intentioned of us sometimes have to just give our projects a lick and a promise. \"Proper test coverage soon\", you sweetly croon as you rock it to sleep, the knowledge that you're telling a dark, terrible lie twisting you up inside. Maybe you could just scrape enough budget together for some simple unit tests? Then, at least, you'd have \"tests\", right?",[2339,2338,2775,2776],"e2e","playwright","/ckeefer/2024-3/img/E2E_Testing_2024.png","2024-06-15","content:ckeefer:2024-3:e2e_testing.md",{"user":2473,"name":2474},{"_path":2782,"title":2783,"description":2784,"tags":2785,"image":2787,"publishDate":2788,"_id":2789,"author":2790},"/ckeefer/2024-7/vpubsub","Vue 3 Pub / Sub: All aboard the (event) bus","We like Vue at A+L. We think it's one of the best frontend frameworks, and a great choice pretty much anywhere you might otherwise be tempted to use React.",[2470,2767,2786],"pub/sub","/ckeefer/2024-7/img/event_bus.png","2024-08-15","content:ckeefer:2024-7:VPubSub.md",{"user":2473,"name":2474},{"_path":2792,"title":2793,"description":2794,"publishDate":2795,"tags":2796,"_id":2797,"author":2798},"/cmacksey/2012-5/php-musings","PHP Musings","Ran into an interesting, but thorough, rant the other day - PHP: A Fractal of Bad Design. The part that grabbed me the most was the analogy at the beginning, which was all too perfect:","2012-05-07",[2565],"content:cmacksey:2012-5:php-musings.md",{"user":2799,"name":2800},"cmacksey","Chris Macksey",{"_path":2802,"title":2803,"description":2804,"publishDate":2805,"tags":2806,"_id":2808,"author":2809},"/dpopowich/2021-07-30/data-collector","Asynchronous Python - A Real World Example","A dive into a real example of async Python usage.","2021-07-30",[14,2594,2807],"async","content:dpopowich:2021-07-30:data-collector.md",{"user":2810,"name":2811},"dpopowich","Daniel Popowich",{"_path":2813,"title":2814,"description":2815,"tags":2816,"image":2818,"publishDate":2819,"_id":2820,"author":2821},"/dpopowich/2023-8/postgres-pubsub","Using PostgreSQL for Pub/Sub","A+L has been working on a Single Page Application (SPA) wherein our client's users take on the role of Staff Users (think: project managers) as they aid their Customer Users in using the application to complete a complex project.",[2817,14,2807],"postgresql","/dpopowich/2023-8/img/psql_pub_sub.png","2024-04-15","content:dpopowich:2023-8:postgres-pubsub.md",{"user":2810,"name":2811},{"_path":5,"title":9,"description":10,"tags":2823,"image":15,"publishDate":16,"_id":2302,"author":2824},[12,13,14],{"user":2308,"name":2309},{"_path":2826,"title":2827,"description":2828,"publishDate":2829,"image":2830,"tags":2831,"_id":2832,"author":2833},"/ewahl/2025-06/argued_with_ai","I Argued With an AI for 20 Minutes About Async Code &mdash; And I'm Surprisingly Happy","If you have ever spent twenty minutes debating an obscure AWS Lambda invocation pattern with an AI, you might question your life choices. But here I am: amused by the wasted time but ultimately happy with the outcome and understanding I gained.","2026-06-01","/ewahl/2025-06/img/argued_with_ai.png",[12,13,14],"content:ewahl:2025-06:argued_with_ai.md",{"user":2308,"name":2309},{"_path":2835,"title":2836,"description":2837,"tags":2838,"image":2839,"publishDate":2439,"_id":2840,"author":2841},"/jbagley/2019-4/makingspectrogramsinjuce","Making Spectrograms in JUCE","Art+Logic's Incubator project has made a lot of progress. In a previous post I mentioned that Dr. Scott Hawley's technique to classify audio involved converting audio to an image and using a Convolution Neural Network (CNN) to classify the audio based on this image. That image is a spectrogram. I'm going to go into some detail about what we do to create one, and why to the best of my ability.",[2356,2431,2757],"/jbagley/2019-4/img/Fortissimo_Trumpet_Ensemble_Matrix_Swells_61.wav-2048x1700.png","content:jbagley:2019-4:MakingSpectrogramsInJUCE.md",{"user":2842,"name":2843},"jbagley","Jason Bagley",{"_path":2845,"title":2846,"description":2847,"tags":2848,"image":2851,"publishDate":2852,"_id":2853,"author":2854},"/jbagley/2021-07/softwaresenescence","Legacy Vulnerabilities AKA Software Senescence","Does your business still have an XT computer in the back office because it's\nrunning that one version of some database software that your business depends\non? Yeah, we know there is. Most modern software doesn't work like that.",[2849,2850],"legacy","project-management","/jbagley/2021-07/img/old_software_to_new.jpg","2021-07-01","content:jbagley:2021-07:SoftwareSenescence.md",{"user":2842,"name":2843},{"_path":2856,"title":2857,"description":2858,"tags":2859,"image":2861,"publishDate":2862,"_id":2863,"author":2864},"/jbagley/2021-08-01/accuratetiming","Accurate Timing","In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results.",[2431,2860],"timing","/jbagley/2021-08-01/img/accurateTiming.jpg","2021-08-01","content:jbagley:2021-08-01:AccurateTiming.md",{"user":2842,"name":2843},{"_path":2866,"title":2867,"description":2868,"tags":2869,"image":2873,"publishDate":2874,"_id":2875,"author":2876},"/jbagley/2023-06-01/universal_ffmpeg_custom_builds","Building Universal FFmpeg Custom Binaries","I am using a very pared down set of FFMpeg features for a macOS project that I\nbuild into a custom library. I had a script set up to configure the build which\nworked fine on my Intel based MacBook Pro. Then I upgraded to an Apple Silicon\nMacBookPro and wanted to run natively, or at least see what happened when I\ndid. To build, FFMpeg uses autoconf which produces a makefile that then handles\nthe build.",[2870,2871,2872,2354],"c","bash","ffmpeg","/jbagley/2023-06-01/img/header.png","2024-04-01","content:jbagley:2023-06-01:Universal_FFMPEG_custom_builds.md",{"user":2842,"name":2843},{"_path":2878,"title":2879,"description":2880,"publishDate":2881,"image":2882,"tags":2883,"_id":2887,"author":2888},"/jbagley/2025-08/a_developers_primer_on_apple_tracking_transparency","A Primer on Apple's App Tracking Transparency","If an app tracks user activity, Apple requires them to declare all information they collect as well as whether that data is linked or tracked. This includes collection by the app itself and any third parties the app uses. The app owner is responsible for knowing and correctly reporting privacy information for all components in the app.","2026-05-22","/jbagley/2025-08/img/apple_app_transparency.png",[2884,2885,2353,2886],"app tracking transparency","att","macos","content:jbagley:2025-08:a_developers_primer_on_apple_tracking_transparency.md",{"user":2842,"name":2843},{"_path":2890,"title":2891,"description":2892,"tags":2893,"image":2897,"publishDate":2898,"_id":2899,"author":2900},"/jestep/2023-3/fastapi","FastAPI: A High-Performance Python Framework for Rapid Web Development","FastAPI is a modern and high-performance Python web framework designed specifically for building APIs and web applications quickly and efficiently. Developed by Sebastián Ramírez and first released in 2018, FastAPI has rapidly gained traction in the developer community thanks to its focus on providing key features for API and web app development with excellent performance.",[14,2894,2655,2895,2896],"fastapi","flask","pyramid","/jestep/2023-3/img/header.png","2024-05-01","content:jestep:2023-3:fastapi.md",{"user":2901,"name":2902},"jestep","Jagger Estep",{"_path":2904,"title":2905,"description":2906,"publishDate":2907,"tags":2908,"image":2912,"_id":2913,"author":2914},"/nharrison/2012-07/core-data","Securing Your Core Data with Transformable Attributes","In order to store private data in an iOS Core Data database, there are several methods available for encryption, including:","2012-07-30",[2909,2910,2353,2368,2911],"core-data","encryption","objective-c","/nharrison/2012-07/img/superman.jpg","content:nharrison:2012-07:core-data.md",{"user":2915,"name":2916},"nharrison","Noah Harrison",{"_path":2918,"title":2919,"description":2920,"tags":2921,"publishDate":2926,"image":2927,"_id":2928,"author":2929},"/phendry/2019-3/restfromthebottomup","REST from the Bottom Up","The RESTful API has a funny place in the software development world: it's widely regarded as the best general-purpose pattern for building web application APIs, and yet it's also nebulous enough of a concept to cause endless disagreements within teams over exactly how to implement one.",[2922,2923,2924,2925],"rest","api","web","architecture","2019-10-01","/phendry/2019-3/img/feature_image.png","content:phendry:2019-3:RestFromTheBottomUp.md",{"user":2930,"name":2931},"phendry","Paul Hendry",{"_path":2933,"title":2934,"description":2935,"tags":2936,"publishDate":2852,"image":2937,"_id":2938,"author":2939},"/phendry/2021-06/smoothupgradestovue3","Smooth Upgrades to Vue 3","This post assumes basic familiarity with Vue.js v2.x.",[2470,2767,2594],"/phendry/2021-06/img/vue-transition.jpg","content:phendry:2021-06:SmoothUpgradesToVue3.md",{"user":2930,"name":2931},{"_path":2941,"title":2942,"description":2943,"image":2944,"tags":2945,"publishDate":2805,"_id":2946,"author":2947},"/phendry/2021-07-30/spotthevulndataranges","Spot the Vulnerability: Data Ranges and Untrusted Input","In 1997, a flaw was discovered in how Linux and Windows handled IP fragmentation, a Denial-of-Service vulnerability which allowed systems to be crashed remotely.","/phendry/2021-07-30/img/vulnerability.jpg",[2368,2318],"content:phendry:2021-07-30:SpotTheVulnDataRanges.md",{"user":2930,"name":2931},{"_path":2949,"title":2950,"description":2951,"tags":2952,"image":2953,"publishDate":2954,"_id":2955,"author":2956},"/phendry/2021-08-15/exploringdependenttypesinidris","Exploring Dependent Types in Idris","When I'm not coding the \"impossible\" at Art+Logic, I take a lot of interest in new programming technologies and paradigms; even if they're not yet viable for use in production, there can often be takeaways for improving your everyday code.",[2505],"/phendry/2021-08-15/img/dependent-types.jpg","2021-08-15","content:phendry:2021-08-15:ExploringDependentTypesInIdris.md",{"user":2930,"name":2931},{"_path":2958,"title":2959,"description":2960,"tags":2961,"image":2962,"publishDate":2963,"_id":2964,"author":2965},"/phendry/2021-10-30/spotthevulnloopsandtermconditions","Spot the Vulnerability: Loops and Terminating Conditions","In memory-unsafe languages like C, special care must be taken when copying untrusted data, particularly when copying it to another buffer. In this post, we'll spot and mitigate a past vulnerability in Linux's NTP daemon.",[2368,2318],"/phendry/2021-10-30/img/vulnerability-2.jpg","2021-10-30","content:phendry:2021-10-30:SpotTheVulnLoopsAndTermConditions.md",{"user":2930,"name":2931},{"_path":2967,"title":2968,"description":2969,"image":2970,"tags":2971,"publishDate":2972,"_id":2973,"author":2974},"/phendry/2022-07-21/migratingfromexpresstofastifypart1","Migrating from Express to Fastify, Part 1","Express.js has for years been the dominant lightweight Web framework for Node.js, but over time its development has stalled, with its latest major version (5.0) still in pre-release nearly eight years after its first alpha release. There's a lot to be said for this sort of stability in a foundational dependency for a project, but it's worth assessing whether the added features of competing frameworks are worth making a switch. In this article we'll be looking at Fastify in particular, to understand what it has to offer compared to Express and how difficult it is to migrate an existing Express project.","/phendry/2022-07-21/img/Migrating from Express to Fastify, Part 1.png",[2470,2318],"2023-12-01","content:phendry:2022-07-21:MigratingFromExpressToFastifyPart1.md",{"user":2930,"name":2931},{"_path":2976,"title":2977,"description":2978,"image":2979,"tags":2980,"publishDate":2981,"_id":2982,"author":2983},"/phendry/2022-07-28/migratingfromexpresstofastifypart2","Migrating from Express to Fastify, Part 2","In Part 1, we looked at the features of the Fastify Node.js Web framework compared to Express.js. In Part 2, we'll work through migrating an example Express.js application to Fastify.","/phendry/2022-07-28/img/Migrating from Express to Fastify, Part 2.png",[2470,2318],"2023-12-31","content:phendry:2022-07-28:MigratingFromExpressToFastifyPart2.md",{"user":2930,"name":2931},{"_path":2985,"title":2986,"description":2987,"tags":2988,"image":2989,"publishDate":2990,"_id":2991,"author":2992},"/phendry/2023-01-19/badcode","\"Bad\" Code (Or, Why Software Development is Hard)","Recently, the Dutch government open-sourced the iOS application for their \"DigiD\" authentication service. A tweet with a snippet of that source code, presumably making fun of it, blew up into a debate about whether mocking it is even justified. The amount of debate over such a simple snippet of code highlights, in my mind, just how tricky software development can be.",[2368,2318],"/phendry/2023-01-19/img/Bad Code.png","2024-01-15","content:phendry:2023-01-19:BadCode.md",{"user":2930,"name":2931},{"_path":2994,"title":2995,"description":2996,"image":2997,"publishDate":2998,"tags":2999,"_id":3000,"author":3001},"/phendry/2023-01-31/forgetaboutcodestyle","Forget About [Code] Style","Good code style, being highly subjective, is something often debated among developers. After all, we spend more time reading code than writing it, so it's worth making sure our code is styled to be as easy as possible to read and to understand. On the other hand, deciding upon and continuously enforcing a style is also time-consuming, and the benefits are near-impossible to quantify. Given that modern code formatting tools can fully automate the process, is it still worth fretting about style?","/phendry/2023-01-31/img/forget_style_header.png","2024-02-01",[2505],"content:phendry:2023-01-31:ForgetAboutCodeStyle.md",{"user":2930,"name":2931},{"_path":3003,"title":3004,"description":3005,"image":3006,"tags":3007,"publishDate":3008,"_id":3009,"author":3010},"/phendry/2023-04-02/semantichtml","Don't Give Up on Semantic HTML","Since the early days of the Web, there has been tension between the ideal of \"semantic HTML\" and the practical reality of designing complex page layouts, which often could not be achieved without inserting style concerns into the document. More recently, frameworks like Tailwind CSS have emerged which challenge the very idea that semantic HTML is an ideal to strive for, and which commit to thoroughly embedding style concerns into HTML documents. With modern CSS features however, semantic HTML is more achievable than ever, and I do think it remains a worthy goal.","/phendry/2023-04-02/img/Don't Give Up on Semantic HTML.png",[2505],"2024-03-01","content:phendry:2023-04-02:SemanticHtml.md",{"user":2930,"name":2931},{"_path":3012,"title":3013,"description":3014,"image":3015,"tags":3016,"publishDate":3017,"_id":3018,"author":3019},"/phendry/2023-05-16/doyouneedacsspreprocessor","Do You Need a CSS Preprocessor in 2023?","CSS preprocessors like Less, Sass and Stylus have long provided powerful features that vanilla CSS lacked: variables, nesting of rulesets, mixins, control flow constructs, etc. These days however, the feature gap is considerably narrower, and it's not so clear that the benefits of a preprocessor outweight the burdens of setting it up.","/phendry/2023-05-16/img/css_preprocessor_header.png",[2505],"2023-01-01","content:phendry:2023-05-16:DoYouNeedACSSPreprocessor.md",{"user":2930,"name":2931},{"_path":3021,"title":3022,"description":3023,"image":3024,"publishDate":3025,"tags":3026,"_id":3027,"author":3028},"/phendry/2023-07-28/dependencymanagement","Software Dependency Management: Best Practices","Leveraging third-party libraries and frameworks is essential in most modern software projects, and the projects we build at Art+Logic are no exception. The pressure on developers to rapidly deliver features is high, and there are so many commonalities in the details of each project (particularly in Web development) that a lot of development time can be saved by using well-designed libraries that handle the details.","/phendry/2023-07-28/img/dependency_header.png","2023-01-02",[2505],"content:phendry:2023-07-28:DependencyManagement.md",{"user":2930,"name":2931},{"_path":3030,"title":3031,"description":3032,"tags":3033,"publishDate":3036,"image":3037,"_id":3038,"author":3039},"/phendry/2023-11-06/frontendframeworksin2024","Frontend Frameworks in 2024: React, Svelte and Vue","Several years ago, Art+Logic settled on Vue.js as our preferred frontend Web framework. Now, in 2024, we feel it's time to revisit the frontend framework landscape to see how things have (or haven't) changed.",[2505,3034,3035,2767],"react","svelte","2024-05-15","/phendry/2023-11-06/img/frontend_frameworks_2024.png","content:phendry:2023-11-06:FrontendFrameworksIn2024.md",{"user":2930,"name":2931},{"_path":3041,"title":3042,"description":3043,"publishDate":3044,"tags":3045,"image":3048,"_id":3049,"author":3050},"/rbrubaker/2012-06/arduino-thermometer","Turn Your Mac into a Thermometer with Arduino","The topic of the Arduino came up around A&L's \"virtual water cooler\" last week. About a year and a half ago, I purchased a SparkFun Inventor's Kit for Arduino. The kit is a fun way for a hardware novice like me to get started and learn some basics. It comes with more than a dozen sample projects such as lighting LEDs, spinning a motor and generating audio.","2012-06-28",[3046,3047],"arduino","java","/rbrubaker/2012-06/img/arduino1.jpg","content:rbrubaker:2012-06:arduino-thermometer.md",{"user":3051,"name":3052},"rbrubaker","Ryan Brubaker",{"_path":3054,"title":3055,"description":3056,"publishDate":3057,"tags":3058,"_id":3061,"author":3062},"/rbrubaker/2012-06/coffe-backbone-1","Fun with CoffeeScript and Backbone.js : Part 1","CoffeeScript has been all the rage lately and I've been wanting to hop on board the bandwagon. I've also seen Backbone.js mentioned quite a bit and was even more intrigued after listening to this .NET Rocks podcast. I decided to convert some plain JavaScript code I had in a side project to use both CoffeeScript and Backbone.js and see how things went.","2012-06-06",[3059,3060,2495,2318],"backbone-js","coffeescript","content:rbrubaker:2012-06:coffe-backbone-1.md",{"user":3051,"name":3052},{"_path":3064,"title":3065,"description":3066,"publishDate":3067,"tags":3068,"_id":3069,"author":3070},"/rbrubaker/2012-06/coffee-backbone-2","Fun with CoffeeScript and Backbone.js : Part 2","In this post I’ll discuss the code that handles updating the UI.","2012-06-07",[3059,3060,2495,2318],"content:rbrubaker:2012-06:coffee-backbone-2.md",{"user":3051,"name":3052},{"_path":3072,"title":3073,"description":3074,"publishDate":3075,"tags":3076,"_id":3077,"author":3078},"/rbrubaker/2012-06/coffee-backbone-3","Fun with CoffeeScript and Backbone.js : Part 3","In this post I’ll discuss my thoughts on CoffeeScript and Backbone.js.","2012-06-08",[3059,3060,2495,2318],"content:rbrubaker:2012-06:coffee-backbone-3.md",{"user":3051,"name":3052},{"_path":3080,"title":3081,"description":3082,"publishDate":3083,"tags":3084,"_id":3085,"author":3086},"/rbrubaker/2012-07/prototypal-js","Prototypal vs. Functional Inheritance in JavaScript","If you ever found JavaScript's prototypal inheritance confusing, do yourself a favor and open this article, open a JavaScript console and code each example in the article. You will definitely come away with a better understanding of how prototypal inheritance works in JavaScript.","2012-07-11",[3060,2470],"content:rbrubaker:2012-07:prototypal-js.md",{"user":3051,"name":3052},{"_path":3088,"title":3089,"description":3090,"publishDate":3091,"tags":3092,"_id":3093,"author":3094},"/rbrubaker/2012-07/whither-pm","Whither Project Management?","When I was first asked to manage a project at Art & Logic, I had my reservations. Did I really want to start down a career path that led to less development? Would my skills as a developer go stale? My first few projects as a manager were solo projects so I still had plenty of development work and fortunately, I found myself to be a pretty easy person to manage. As time went on I started managing larger projects and with them came the responsibility to manage other developers. To my surprise I found project management to be rewarding and dare I say, even fun. It's very satisfying to work with clients, helping them define their visions and seeing those visions come to life.","2012-07-25",[2850],"content:rbrubaker:2012-07:whither-pm.md",{"user":3051,"name":3052},{"_path":3096,"title":3097,"description":3098,"tags":3099,"image":3102,"publishDate":2439,"_id":3103,"author":3104},"/scharette/2019-4/discover_machine_learning","Discover Machine Learning","Computers have been around for less than 100 years.  In that short period of time, some incredible things have happened:  they've been universally adopted so quickly that we have them in our houses.  In our cars.  Even in our pockets.  In the last 40 years, there have been many significant events when it comes to computers:",[3100,3101,2431],"machine-learning","neural-networks","/scharette/2019-4/img/discover_machine_learning.png","content:scharette:2019-4:discover_machine_learning.md",{"user":3105,"name":3106},"scharette","Stéphane Charette",{"_path":3108,"title":3109,"description":3110,"publishDate":3111,"image":3112,"tags":3113,"_id":3115,"author":3116},"/shuey/2012-05/baas","BaaS Offerings Continue to Grow","The makers of Simplenote recently introduced their Backend as a Service (BaaS) offering called Simperium that looks to compete in an increasingly crowded space with services like CloudMine, Kinvey, and Parse and to some extent with iCloud for iOS and OS X only apps. So just how crowded is this space? Back in February, Kinvey published their own map of the BaaS ecosystem that highlights different tiers of the ecosystem and various relationships between them.","2012-05-10","/shuey/2012-05/img/header.png",[3114],"baas","content:shuey:2012-05:baas.md",{"user":3117,"name":3118},"shuey","Steven Huey",{"_path":3120,"title":3121,"description":3122,"publishDate":3123,"tags":3124,"image":3126,"_id":3127,"author":3128},"/shuey/2012-05/cloud","Under the Sheets with iCloud and Core Data","Drew McCormack is writing a great series (Part 1, Part 2, Part 3) of posts about using iCloud for syncing Core Data managed data. It's harder than Apple lets on and Drew has done a great job of uncovering how this actually works.","2012-05-28",[2354,3125],"icloud","/shuey/2012-05/img/icloud.jpg","content:shuey:2012-05:cloud.md",{"user":3117,"name":3118},{"_path":3130,"title":3131,"description":3132,"publishDate":3133,"tags":3134,"_id":3136,"author":3137},"/shuey/2012-05/economics-android","The Economics of Android","If you haven't already do yourself a favor and head over to asymco.com to catch Horace Deidu's multi-post series on \"The Economics of Android\". Horace and Dan Benjamin discuss the series during this week's Critical Path podcast as well. Horace is a former analyst for Nokia and has been writing Asymco for a few years now. His analysis of the mobile industry and Apple's place within it in particular has been featured in publications such as Bloomberg and Forbes.","2012-05-17",[3135],"android","content:shuey:2012-05:economics-android.md",{"user":3117,"name":3118},{"_path":3139,"title":3140,"description":3141,"publishDate":2795,"tags":3142,"_id":3145,"author":3146},"/shuey/2012-05/iot","The Internet of Things and Big Data","I've been following the developments in the \"Internet of Things\" and Big Data / Open Data markets as new apps and tools are released and they look to be two exciting technologies on a collision course. With the advent of internet connected home appliances like Wattvision and Nest that provide real utility to the average home owner at reasonable prices along with crowd funded projects like Air Quality Egg or Twine we should see an explosion in the kinds and amount of useful and real-time or near real-time data that is available to anyone with a smartphone. Health metric or \"quantitative self\" tracking devices such as Fitbit, Jawbone Up, and the Pebble watch will fuel this data explosion as well.",[3143,3144],"big-data","iot","content:shuey:2012-05:iot.md",{"user":3117,"name":3118},{"_path":3148,"title":3149,"description":3150,"publishDate":3151,"tags":3152,"image":3154,"_id":3155,"author":3156},"/shuey/2012-05/rubymotion","RubyMotion Brings Ruby to iOS","RubyMotion is a new development toolchain that allows you to build iOS apps using Ruby created by Laurent Sansonetti, a former Apple engineer and contributor to the MacRuby project. It has garnered a lot of attention the past few weeks and some detailed reviews have already been written:","2012-05-14",[2353,3153],"ruby","/shuey/2012-05/img/logotype-icon.png","content:shuey:2012-05:rubymotion.md",{"user":3117,"name":3118},{"_path":3158,"title":3159,"description":3160,"publishDate":3161,"tags":3162,"image":3163,"_id":3164,"author":3165},"/shuey/2012-06/thoughts-ios6","A few thoughts on iOS 6","Apple made their session videos from WWDC 2012 available earlier this week in record time. It's nice to see since tickets for this years event sold out in under two hours. Apple has an iOS 6 Preview page touting some of the new features such as Siri's new abilities, tighter integration with Facebook, Photo Stream sharing, and things like iCloud tabs for Safari all of which look great.","2012-06-21",[2354,2353],"/shuey/2012-06/img/ios6.jpg","content:shuey:2012-06:thoughts-ios6.md",{"user":3117,"name":3118},{"_path":3167,"title":3168,"description":3169,"publishDate":3170,"tags":3171,"image":3173,"_id":3174,"author":3175},"/shuey/2012-07/mixer","A Simple Mixer Using AVFoundation","In iOS 4.0 Apple introduced the AV Foundation APIs that made working with audio and video media much easier than it had been in previous versions of iOS. Apple then brought these APIs to Mac OS X in OS X 10.7 \"Lion\". In this post I'll show how to use some of the APIs to create a simple four track mixer.","2012-07-02",[2354,3172,2353],"cocoa","/shuey/2012-07/img/mixer-screenshot.jpg","content:shuey:2012-07:mixer.md",{"user":3117,"name":3118},{"_path":3177,"title":3178,"description":3179,"publishDate":3180,"tags":3181,"image":3182,"_id":3183,"author":3184},"/shuey/2012-07/reset-button","The Reset Button","Horace Dediu of Asymco has been publishing some fantastic insights and analysis of the mobile market in the past few weeks. I linked to some of Dediu's analysis of the Economics of Android in an earlier post, and since then he's updated his work with the latest data and is studying RIM and Microsoft's efforts in the space as well.","2012-07-19",[3135,2354,2353],"/shuey/2012-07/img/kevin.jpg","content:shuey:2012-07:reset-button.md",{"user":3117,"name":3118},{"_path":3186,"title":3187,"description":3188,"publishDate":3189,"tags":3190,"image":3192,"_id":3193,"author":3194},"/tfarrel/2012-07","Looking at Steganography","With the help of one of my favorite news aggregators, I discovered this article on using JavaScript and the canvas element to hide information inside images. I've long been fascinated by steganography and this article and demonstration makes it even more accessible. If you can't be bothered to read the article, it describes a method of using the HTML5 File API and the canvas element to embed a message in images.","2012-07-24",[3191],"steganography","/tfarrel/2012-07/img/white.png","content:tfarrel:2012-07:index.md",{"user":3195,"name":3196},"tfarrel","Troy Farrel",1780330263266]