Account boundaries are your strongest isolation primitive. A practical structure for workload, security, and shared-services accounts, expressed entirely in Terraform.

Early on, it's tempting to run everything in a single AWS account with IAM policies and tags doing the work of separation. I've moved away from that model every time, because an account boundary is a fundamentally stronger isolation primitive than a policy. A misconfigured IAM policy or a compromised credential in a single-account setup has a blast radius that spans your entire footprint: production, staging, and shared tooling all sit behind the same perimeter. Split those workloads across accounts and a mistake in one environment simply can't reach another. There's no policy to misconfigure your way around a hard account boundary. It also makes least privilege easier to reason about (permissions are scoped to what an account contains, not enumerated resource by resource), and it makes cost attribution trivial, since every dollar in an account belongs to a known team or workload without tagging discipline doing the heavy lifting.
The structure I default to has a management account at the root purely for billing and organization-wide policy, a dedicated security and log-archive account that receives read-only copies of every other account's logs and holds no workloads of its own, a shared-services account for things like a container registry or CI runners that every environment needs but shouldn't own, and per-environment workload accounts (dev, staging, production) each isolated from the others. This isn't exotic. It mirrors what AWS Organizations and AWS Control Tower expect, and it keeps the mental model simple: if you can name the account, you know what's allowed to happen in it.
None of this is manageable by hand once you have more than two or three accounts, so I express the entire structure in Terraform: reusable modules for the pieces that repeat across accounts (networking, IAM baselines, logging), and account- and environment-specific state, kept separate so a change in staging can never touch production's state file. Rollouts are automated end to end. There is no manual terraform apply from someone's laptop against a live account. If a change needs to happen, it happens through the same reviewed pipeline every time, which is what makes the account structure trustworthy in the first place. The isolation is only as good as the process that maintains it.
Inside that structure, I keep IAM roles scoped to least privilege by default (service roles get exactly the permissions their workload needs, nothing assumed "just in case"), and every account ships its CloudTrail and CloudWatch logs to the central log-archive account, where they're immutable from the perspective of the accounts that generated them. That combination means a compromised workload account can't erase its own evidence, and a review of "what happened" never depends on trusting the account under investigation.