I run a small hosting business for multiple clients, mainly deploying separate static websites with S3 and CloudFront. DNS is managed through Cloudflare, and I use ACM for certificates where needed. Each client has an independent codebase, Git repository, and CI/CD pipeline. I currently use one AWS account per client, with development and production resources separated inside that account. As I grow beyond a handful of clients, I'm considering whether to keep this structure, create separate AWS accounts for each client and environment, or consolidate everything into one account with separate Terraform states. The main factors are isolation and blast radius, account and OU management, Terraform workflows, CI/CD, security, billing attribution, and operational overhead. For a small hosting operation—especially for clients that only need frontend hosting—is one account per client with dev and production resources inside it a sensible long-term design, or is account-per-environment isolation worth the additional complexity?
5 Answers
For the strongest isolation, use a separate account for each client and environment, with production and non-production organized into suitable OUs. An AWS account is the most reliable isolation boundary for resources, permissions, billing, and accidental changes. A development mistake or overly broad Terraform operation is much less likely to affect production or another customer. This model is especially appropriate when clients have different risk profiles, backend services, data, or access requirements. The tradeoff is additional provisioning and account-management work, so automate account creation and baseline configuration if you choose it.
A shared account can work when all sites are essentially copies of the same service, customers never receive AWS access, and you have strong automation and tagging. If you consolidate, keep each client in a separate Terraform state and use tightly scoped deployment roles rather than one broad pipeline. A single state for every client is the riskiest option: state operations become harder to review, one mistake can affect unrelated sites, and billing and ownership become more difficult to track. Account-level separation is still safer because resource-level boundaries—particularly for services such as CloudFront—are not always clean or comprehensive.
The right answer depends heavily on whether the sites are independent deployments or a single multi-tenant product. If each customer has their own development work, pipeline, permissions, or backend, separate client and environment accounts are easier to reason about. If you are only deploying many nearly identical static frontends and customers have no AWS access, separate accounts per client may be more isolation than you need. In either case, keep production deployment roles and Terraform permissions narrowly scoped, and automate the repeated setup.
For simple static sites managed by a small or solo team, one account per client with dev and production separated inside it is a reasonable compromise. It gives you client-level blast-radius containment without immediately doubling the number of accounts. Separate environment accounts make more sense when a client has sensitive data, significant backend infrastructure, independent credentials, compliance requirements, or a genuine need for stronger production isolation. At around ten frontend-only sites, I would not consider the current model obviously wrong.
Remember that many-account designs have practical limits and operational costs. New account creation, organization-wide security controls, logging, billing, certificate management, and account closure all need processes and automation. Start with one account per client if that matches your current scale, then split dev and production into separate accounts for clients that grow in complexity or require stronger isolation. That gives you a gradual migration path instead of accepting the overhead of separate accounts for every environment immediately.

The shared-account model is attractive for reducing administration, but it requires excellent tagging, state isolation, IAM boundaries, backups, and deployment safeguards. Without those controls, a cross-client incident or accidental destroy becomes much more difficult to contain.