Skip to main content

3 posts tagged with "aws"

View All Tags

Derrops Guide to Naming Conventions and Segregation

· 27 min read
Derrick Futschik (Derrops)
Head of Engineering, Fortiro

I've been meaning to make this guide, having worked at a SAAS from the very get-go, I've now got to live with some good and some bad decisions. It's been very hard to pinpoint why the naming conventions have lead to complexity. And overtime I realized that with engineering, you often make decisions based on the tradeoffs, but ultimately you do optimize for something. If you don't figure out what that something should be, you end having a solution which is not optimized for the problem you are trying to solve.

Living with a SAAS for many years now start to finish, what I ended up finding that these conventions are more important than I initially thought, and have ramifications in many different areas:

AreaDescriptionJustification
CostAbility to optimize, track, and control spend across environments, services, or resources.Consistent naming and segregation make it easier to accurately allocate and monitor resource usage by environment, service, or team.
SecurityHow easily you can enforce and audit security boundaries and practices.Segregation and clear naming provide obvious boundaries for applying Least Privilege, monitoring access, and investigating incidents.
ComplexityThe overhead and cognitive load of understanding and managing environments and resources.Predictable conventions reduce ambiguity and make it simpler for everyone to locate, manage, and reason about resources.
MaintainabilityHow straightforward it is to update, refactor, or scale your environments and naming conventions.Uniform conventions mean changes can be made systematically, minimizing the risk of errors and excessive rework.
ScalabilityAbility to grow the number of services, accounts, or environments without hitting convention or collision issues.Good conventions prevent collisions and manual workarounds as your organization and environments scale.
UsabilityHow easy naming and segregation is for engineers and consumers to work with daily.Easy-to-understand resource names speed up onboarding and reduce misconfiguration by new and experienced engineers alike.
PerformanceInfluence of structure/naming on ability to optimize for latency, throughput, or efficiency.Logical segregation enables more effective resource grouping, which can positively impact locality, caching, or policy-based optimizations.
ReliabilityImpact on operational stability, incident response, and minimizing blast radius of issues.Separation by environment/service/domain helps contain failures and allows more targeted incident response.
AvailabilityAffect on uptime or redundancy due to naming/segregation conventions.Clear segregation facilitates independent deployments and failover, increasing uptime and resilience.
ComplianceHelps enforce regulatory, audit, and governance requirements more easily.Accurate, well-segregated naming enables easier reporting, automated policy enforcement, and audit compliance.

These areas can all be condensed into the following guiding principals

Guiding Principles

1. Naming Consistency Principle

Definition:
A given resource should have the same name in every environment—whether it’s dev, prod, or any other. For example, a resource called user-service should be named user-service in all environments, rather than user-service-dev or user-service-prod. Environment must be represented by namespace isolation, not naming convention.

Why it matters:
Consistent resource names dramatically simplify the process of grouping, querying, and managing deployment instances across various tools and platforms. It helps reduce cognitive load and operational complexity, especially as your infrastructure grows.

note

Globally unique name, such as with an s3 bucket, it can be that another customer on AWS takes the name of an S3 bucket that you had planned. One solution to this is including a random id within the name. But in general I've found this scenario rare, and losing predictability in the name which can lead to negative outcomes

note

Global services such as IAM which are global also may cause conflicts if you were to have more than 1 environment in an account Prefer Account per Environment if Possible


2. Naming Stability Principle

Definition:
Prioritize naming conventions that minimize how often resource names need to change over the lifetime of your systems. Choose prefixes or structures for your names that are unlikely to require revision as your organization and requirements evolve.

Why it matters:
Stable naming reduces refactoring, lowers risk of errors, and leads to smoother automation and scaling. The fewer changes needed, the more reliable and maintainable your environment will be in the long run. You'll also find that following this principal results in a better security posture, more on that later.

Segregation Strategy

Segregation needs to be done in a strategic way. Resources segregated differently can lead to drastically different outcomes. Following a consistent pattern for Segregation and Naming Conventions can lead ot many different benefits:

  • To avoid naming collisions
  • To reduce complexity by not adding unnecessary naming conventions to resources.
  • To make it easy to group resources across environments together. This is useful in filtering duplicates for security findings, as having different names for the same resources adds complexity to the queries needed
  • Easier to automate as resources are easier to target across different environments as they are named in a predictable way.
  • Within an environment, segregating services and organizations sharing an environment increases your security posture, as the prefixes can be used to scope access to resources and create access boundaries

Account Segregation

How you segregate your account will determine your name space, and therefore will affect your naming conventions. When creating accounts, modern patters would be to have 1 per region and environment, but there may be cases where you take a different approach again because of other factors related to: (*Cost&, Compliance, Security, etc). But do challenge this decision if you do not segregate, as there is now especially strong multi-account support in Cloud Providers such as AWS.

In general the more you segregate, the less likely you'll have naming collisions.

SegmentNameDescription
Region{region}If you don't segregate your Account by region, you will need to have {region} in the name of the resource to avoid collisions.
Environment{env}If you don't segregate your Account by environment, you will need to have {env} in the name of the resource to avoid collisions.
tip

Operationally if Deployment Instances have different names, it can create severe complexity when trying to groups those instances together in different DevOps tools and products. I've experienced this first hand when sifting through findings in AWS Security Hub. Just when you find a way within a given tool to group these resources and solve this, as your organization grows and adopts new tools you find yourself being faced with this issue again and again for every tool you use.

note

Consider comparing 2 x Lambda functions foobar, in 2 different environments dev and prod. If the Deployment Instances have different names, your query will need to have an OR clause to include both, foobar-dev or foobar-prod and then also have the group by environment anyway. Whereas if they have the same name, there is no need for a clause on the name. Excluding environment reduces the mental load in many different scenarios.

Prefixing

Segregation Hierarchy

Prefix Structure

Structuring your names correctly is key to the success of your naming conventions.

If your config store is NOT located in the same namespace as the resource, you will have to have {env} in your naming conventions. It's challenging where to put {env}, as there are tradeoffs Also note if your config store is NOT in the same {region}, then you will need to have {region} in your naming conventions.

Segment Definitions

SegmentExampleDescription
{region}ap-southeast-2(Optional if platform will be multi-region) The region of the deployment. This may be required if you are not going to segregate your regions by accounts, then there will be conflicts as some services are global (AWS IAM) or share the same namespace (such as S3 Buckets which must be unique across all regions). Deciding to have different accounts for different regions mitigates this issue, but this decision can sometimes be taking other factors into account than this guide. Note this is the Datacenter Region (e.g. ap-southeast-2), not the country itself.
{env}dev(Only if Required) The deployment environment. Only required in the prefix, when the config key lives in a different namespace than the resource accessing it. If your config is stored in the same account or namespace as the resource, this segment is redundant and should be omitted. However if a globally unique name is required, such as with an s3 bucket, this this will be required in the prefix.
{org}acmeThe top-level tenant or business unit. Provides hard namespace isolation. Chosen to be stable and long-lived—changes are rare and treated as a migration event.
{domain}paymentsA bounded business or technical capability that can be owned and reasoned about independently. More stable than a team name, more meaningful than a platform label.
{service}checkout-apiThe concrete, deployable unit within a domain. Specific enough to be unambiguous, broad enough to own multiple configuration values beneath it.
{partition}2024/01/15/14(Optional — data partitioning only) A runtime-determined subdivision used to group high-volume data into discrete, filterable sets. Most common in data storage contexts such as S3 log or event data. {partition} is the only segment permitted to contain internal hierarchical delimiters — a single partition can represent multiple levels of granularity (e.g. {year}/{month}/{day}/{hour}), each of which remains a valid, queryable prefix boundary. All other segments must be flat and contain no internal delimiters. Not applicable to config stores or resource naming.
{key}stripe-webhook-secretThe final, addressable artifact. Everything above it is context and namespace — {key} is the specific thing being referenced. The form varies by system: a config parameter name (stripe-webhook-secret), a filename (transactions.json), or an image tag (1.2.3) — but the concept is always the same: it uniquely identifies the artifact within its namespace. A new segment is only warranted when it creates a meaningful operational boundary — a prefix you would independently query. System-generated uniqueness suffixes (e.g. sequence numbers appended by parallel writers like Kinesis Firehose or Spark) do not qualify; they carry no business meaning and are simply part of {key}, but do not make up part of the identity.

Segment Order

It is sometimes said:

Good architecture makes change easy. Bad architecture makes change hard.

Therefore in this decision we should optimize for making Change Easy, which would dictate the

stability should decrease left to right in a prefix

  • Changing leftmost segments causes the greatest disruption.
  • Renaming service only affects its subtree.

Therefore it makes sense the order of the segments should be the most stable to the least stable:

Segment Stability

SegmentQuestionStabilityScope BoundaryStability
{region}Where in the world?Extremely HighInfrastructure locality boundaryfor all intensive purposes will never change
{env}Which deployment stage?Extremely HighDeployment lifecycle boundaryfor all intensive purposes will never change
{org}Who owns the resource?Very HighOwnership boundarychanges almost never, unless re-org
{domain}What business capability?HighCapability boundarychanges rarely, capabilities outlive teams, only if domain is modelled differently and refactored
{service}Which deployable unit?MediumDeployment unit boundarychanges occasionally, deployable units get renamed, split or merged
{partition}How is data subdivided?Very Low (Optional)Data partitioning boundarychanges constantly; new values generated at runtime (e.g. a new date partition every day). Only relevant for data storage — omit everywhere else
{key}What configuration value?LowConfiguration/value boundarychanges most frequently

Fully Qualified Examples

ExampleValue
Hierarchical (config stores)/ap-southeast-2/prod/acme/payments/checkout-api/stripe-webhook-secret
Compound kebab-case (resource names)/ap-southeast-2/prod/acme/payments/checkout-api/stripe-webhook-secret
Account-segregated (preferred):acme--payments--checkout-api--stripe-webhook-secret
Data storage with partition (S3 object key)acme/payments/checkout-api/2024-01-15/transactions.json

Delimiters

Now that we have segments, and their order, we need to decide on the delimiters to use. Here are some possible candidates evaluated over different resource types. You'll see that the - is most supported. _ is a strong candidate but fails when it comes to host names and S3 bucket names.

Possible Candidates for Delimiters:

DelimiterHostnameAWS Stack Name (CloudFormation)URL PathS3 Bucket NameSafe Across ALL?Notes
- hyphen✅ Yes✅ Yes✅ Yes✅ Yes⭐ Yes (BEST)Universal standard. Recommended everywhere.
_ underscore❌ No✅ Yes✅ Yes❌ No❌ NoBreaks hostname and S3 bucket compatibility
. dot✅ Yes✅ Yes✅ Yes✅ Yes (with caveats)⚠️ ConditionalBreaks TLS wildcard matching, avoid in bucket names
/ slash❌ No❌ No✅ Yes❌ No (delimiter only in key)❌ NoOnly valid as URL path separator
space❌ No❌ No❌ Encoded❌ No❌ NoNever use
-- double hyphen✅ Yes✅ Yes✅ Yes✅ Yes⭐ YesCommon and safe variant

There are different types of delimiters. Sometimes we are representing differences between the different segments of the prefix, other times we are representing differences between the different words with the same segment. Therefore we need a delimiter for each case. As - is the only supported delimiter for all resource types, including another is not ideal because it will reduce the resources types which can follow that convention. Instead the -- is used to delimit between the different segments of the prefix, and a - is used to delimit between the different words with the same segment.

Naming ConventionPriorityResource TypeDelimiterWord DelimiterDescriptionExample
hierarchical-case1Config Keys/-Hierarchical naming convention using a path-style namespace, useful for configuration keys./acme/store/checkout/api-key
compound kebab-case2Resource IDs---Kebab-case is URL and code friendly; more acceptable than underscores for most services, especially for resource IDs.acme--store--checkout--api-feature
  • Often 1 is not possible as a name (for example IAC solutions such as AWS Cloudformation)
  • - is preferred over _ as it is more URL friendly (can appear in the host name where as underscore cannot).
  • -- whilst this doesn't look the most pleasing to the eye, it is more URL friendly and distinguishes between the hierarchial delimiters and word delimiters.
  • other tools will not use -- as a delimiter, so using it as a delimiter in the prefix for a key will not be compatible with other tools.

Native Delimiters

note

If a resource already has native support for a delimiter, it should be used instead of the -- delimiter, such as / in AWS S3, for storing objects. Otherwise you would lose functionality such as using the prefix to filter objects in a bucket.

note

An exception to this rule might be when you plan on centralizing data collection, and you want all data to be stored in a single location. You may want to preemptively segregate the data in each environment already, even though there will be only 1 segment, to simplify the sync operation.

note

We will assume going forward there is only 1 region, so there is no need for segregation by region, but if there wasn't, region would need to be included in the prefix

Full example:

  1. /{org}/{domain}/{service}/{key} => /acme/payments/checkout-api/stripe-webhook-secret
  2. {org}--{domain}--{service}--{key} => acme--payments--checkout-api--stripe-webhook-secret

Rational for Convention

Every prefix is a meaningful operational boundary

Account-segregated (preferred) — config store:

PrefixBoundaryExample Operation
/acme/*Entire orgAudit all config across the org
/acme/payments/*DomainGrant the payments team read access to their config
/acme/payments/checkout-api/*ServiceFetch all config for a service on startup
/acme/payments/checkout-api/stripe-webhook-secretValueRead a specific secret

Account-segregated — data storage with partition (e.g. S3 log data):

PrefixBoundaryExample Operation
acme/Entire orgReplicate all org data to a central bucket
acme/payments/DomainProcess all events owned by payments
acme/payments/checkout-api/ServiceBackfill or reprocess all logs for a service
acme/payments/checkout-api/2024/Partition — yearArchive or aggregate a full year of data
acme/payments/checkout-api/2024/01/Partition — monthLoad a month's data into a warehouse
acme/payments/checkout-api/2024/01/15/Partition — dayReplay a day's events or run a daily job
acme/payments/checkout-api/2024/01/15/14/Partition — hourDownload all log data for a specific hour
acme/payments/checkout-api/2024/01/15/14/transactions-00001.jsonValueFetch a specific log file

Non-account-segregated — env and region included:

PrefixBoundaryExample Operation
/ap-southeast-2/*RegionList all resources in a region
/ap-southeast-2/prod/*EnvironmentAudit all production config
/ap-southeast-2/prod/acme/*OrgFetch all org config in that env
/ap-southeast-2/prod/acme/payments/*DomainGrant payments access to their prod config
/ap-southeast-2/prod/acme/payments/checkout-api/*ServiceFetch all config for a service on startup
/ap-southeast-2/prod/acme/payments/checkout-api/stripe-webhook-secretValueRead a specific secret

Segment Definitions

Region

The region identifies the physical or logical infrastructure region where the deployment instance resides.

Examples:

  • ap-southeast-2
  • us-east-1
  • eu-west-1

Org

The org identifies the top-level organizational boundary. Which department owns the resource.

note

Not the team itself as teams can change more frequently than the org itself, otherwise this becomes too unstable to use as a namespace boundary.

Domain

  • Encodes business capability rather than org structure — capabilities are far more stable than teams or products over time
  • Well understood in modern engineering culture thanks to DDD, Team Topologies, and microservices discourse
  • Naturally guides correct usage — engineers intuitively know whether something belongs to payments or identity
  • Aligns with how most mature orgs already think about their architecture, even if they don't use the word

Service

The service represents the concrete deployable unit. This is the actual runtime component which is typically the primary identity engineers interact with..

Examples:

  • checkout-api
  • auth-service
  • webhook-worker
  • billing-scheduler

Deployment Units could be in the form of:

  • Lambda function
  • Microservice

Key

The key identifies a specific configuration value, secret, resource, or parameter belonging to a service. Represents the exact value being referenced. Everything to the left provides context.

  • Created
  • Deleted
  • Renamed
  • Rotated
note

Optional: This principal, when dogmatically applied may mean that you segregate data in an environment, even when there will only be 1 segment, to simplify the sync operation to a central location later. As the end destination will need to be segmented, but the source data not necessarily. But by segregating the data in the source we achieve this principle.

This contradicts another principle in guiding how to segregate data, which would say if you only were going to have 1 segment, you should not segregate the data in the first place. But if you can see the future, and you know that you are going to combine data together, then you are best off already pre-paring for this scenario so it doesn't hit you later.

Tagging (Suggestions)

Tagging Env

Many would argue that you need to tag every resource with what environment it lives in. I would argue that if you are segregating environments by accounts, then tags are redundant. Tagging resources would then violate the DRY Do Not Repeat Yourself principle, and add to overhead in needing more unnecessary tagging policies. Whilst it's fairly easy to tag resources you manage, sometimes tools or other entities manage resources in your account and it can be problematic to tag them.

Account itself is a stronger way (than tagging) to group resources across an environment together, as an Account is an actual container for resources, and not just a label. It's easier to make mistakes and not tag a resource for some amount of time, cause cost allocation calculations to be incorrect, whereas usage by Account is always accurate.

If there is some other need to tag resources, such as for security or compliance or because of a tool or service you are using. Then have the Account the source of truth and perform batch tagging as needed. This will reduce the Maintainability.

Concrete Examples:

For the naming convention we don't want to have the {env} and {region} because of the Naming Consistency Principle. But because of uniqueness constraints within a namespace this is not always possible. See uniqueness constraints below:

Uniqueness Constraints

ServiceScope BoundaryregionenvExample
Global NamespaceGlobally Unique across all NamespacesAWS S3 Bucket
Global ServiceUnique in all Regions within a NamespaceIAM Role Name
Regional NamespaceUnique in a Region within a Scope BoundaryRDS Instance Identifier

Concrete Example

Resource TypeScopeGlobal?Physical Name{region} required{env} requiredRationale
S3 BucketGlobalap-southeast-2--prod--acme--payments--checkout-api--backup-storageMust be globally unique
Route53 Hosted ZoneGlobalprod.acme.comEach env account is delegated its own subdomain of the apex domain
Route53 RecordZone scopecheckout-api.prod.acme.comDNS global namespace
CloudFront Distribution AliasGlobalcheckout-api.prod.acme.comGlobal DNS namespace
ACM Certificate DomainGlobalcheckout-api.prod.acme.comGlobal DNS namespace
S3 Static WebsiteGlobalprod.acme-checkout-apiGlobal namespace

Following Native Hierarchies

Many systems have their own built-in hierarchical constructs — path delimiters, subdomain delegation, namespace scoping. When a system already provides a hierarchy, map your naming segments onto it rather than encoding structure into flat compound names.

If the system provides a hierarchy, use it. Don't fight it.

The same logical segments ({org}, {env}, {domain}, {service}, {key}) apply regardless of the system — only the direction, delimiter, and order may differ. Mapping onto native hierarchies preserves the system's built-in ability to filter, delegate, and scope by prefix or level.

Why Use Native Hierarchies?

Native hierarchies provide critical operational benefits that flat compound names cannot:

  1. Prefix Filtering & Querying - Systems like S3, SSM, and CloudWatch Logs support prefix-based queries, enabling you to fetch all resources for an org, domain, or service in a single operation
  2. Permission Scoping - IAM and other RBAC systems can grant access via path prefixes, enabling least-privilege access without listing every resource
  3. Console Organization - AWS console and CLI tools can drill-down and organize resources hierarchically, improving usability
  4. Operational Boundaries - Each prefix level becomes a meaningful operational boundary for automation, monitoring, and access control

Priority Decision: Native Hierarchy vs. Compound Naming

When naming a resource, follow this priority:

  1. Does the service support native hierarchy? → Use the native hierarchy with its delimiter (/, ., :, etc.)
  2. No native hierarchy? → Use compound kebab-case with -- segment delimiters and - word delimiters

Example - CloudWatch Metrics (Critical Pattern):

CloudWatch Metrics have THREE naming components, each serving a distinct purpose:

ComponentWhat Goes HereDelimiterExamplePurpose
Namespace{org}/{domain} ONLY/acme/paymentsOrganize metrics by business capability; enables filtering by domain
Dimensionsservice={service} + operational metadataKey-value pairsservice=checkout-api (env via account, NOT dimension)Enable cross-service queries; query "all services in payments with high CPU"
Metric NameSpecific metric being measured- for wordsrequest-count, error-rate, latency-p99Identify what is being measured

❌ WRONG: Encoding service in namespace → Namespace: acme/payments/checkout-api, Metric: request-count

  • Problem: Cannot query across services ("show me high CPU for ALL services in payments")
  • You'd need separate queries for each service

✅ RIGHT: Service as a dimension → Namespace: acme/payments, Dimension: service=checkout-api, Metric: request-count

  • Env handling (account-segregated, RECOMMENDED): Each account's metrics are naturally isolated; no env dimension needed
    • Prod account metrics: acme/payments namespace, service=checkout-api dimension
    • Dev account metrics: acme/payments namespace, service=checkout-api dimension
    • Permission boundary = AWS account; queries automatically scoped by account access
  • Env handling (if NOT account-segregated): Add env dimension for isolation
    • Both environments in same account: service=checkout-api,env=prod vs service=checkout-api,env=dev
    • But you must manage IAM permissions to prevent cross-env visibility
  • Result: Maximize query utility (cross-service queries) while maintaining permission boundaries

Services with Native Hierarchies

ServiceHierarchy TypeDelimiterBenefitExample
S3 Object KeysPath-based/Prefix filtering, object organizationacme/payments/checkout-api/schema.sql
SSM Parameter StorePath-based/GetParametersByPath queries, IAM path scoping/acme/payments/checkout-api/stripe-key
Secrets ManagerPath-based/Organized secrets, prefix filteringacme/payments/checkout-api/db-password
IAM PathsPath-based/Permission scoping, least privilege/acme/payments/checkout-api/
ECRPath-based/Repository organizationacme/payments/checkout-api
CloudWatch LogsPath-based/Log group filtering and organization/acme/payments/checkout-api/logs
CloudWatch MetricsNamespace + Dimensions/ (namespace), key-value (dims)Namespace: acme/payments; Dimension: service=checkout-api; enables cross-service queriesacme/payments (namespace) + service=checkout-api (dimension)
OpenSearchIndex patterns/Time-series index organizationacme/payments/checkout-api/transactions/2024-01-15
Route53 DNSSubdomain.Zone delegation, DNS hierarchycheckout-api.payments.acme.com
KafkaTopic dots.Topic organization and consumer scopingacme.payments.checkout-api.events

DNS

DNS naming is a special case where the hierarchy is reversed compared to prefix-based naming conventions.

Prefix-based systems grow left → right:

/{org}/{domain}/{service}

DNS grows right → left:

{service}.{env}.{org}.com

Both represent the same logical hierarchy in opposite directions.

Core Principle

DNS names must preserve the same logical hierarchy as prefixes, but in reverse order.

PrefixDNS Equivalent
/acme/payments/checkout-apicheckout-api.payments.acme.com
/acme/identity/auth-serviceauth-service.identity.acme.com
/acme/portal/webweb.portal.acme.com

Hierarchy equivalence:

Logical LevelPrefixDNS
Orgacmeacme.com
Domainpaymentspayments.acme.com
Servicecheckout-apicheckout-api.payments.acme.com

Environment Inclusion

Environment appears in DNS between the service and the org:

{service}.{env}.{org}.com

Each environment account is delegated its own subdomain of the apex domain:

checkout-api.prod.acme.com   (prod account owns prod.acme.com)
checkout-api.dev.acme.com (dev account owns dev.acme.com)

The apex domain (acme.com) is managed in a central network or DNS account. Each environment account is delegated a subdomain (prod.acme.com, dev.acme.com), giving that account full ownership and autonomy over its DNS records. This mirrors how account segregation provides namespace isolation — the subdomain is the account's namespace in DNS.

Any new service deployed into an account is automatically under that account's subdomain, with no coordination required at the apex level.

Alternative (apex zone per account, no env in DNS):

checkout-api.payments.acme.com

Same name exists independently in each environment account. Env isolation is provided entirely by the account boundary with no qualifier in the URL.

Route53 Hosted Zone Strategy

Preferred:

The apex domain (acme.com) is managed in a central account. Each environment account is delegated its own subdomain via NS record delegation:

prod.acme.com   (prod account)
dev.acme.com (dev account)
uat.acme.com (uat account)

Services are then addressed as {service}.{env}.{org}.com:

checkout-api.prod.acme.com

Each account has full autonomy over its subdomain. Adding a new service or record requires no changes to the central apex zone. The apex zone only needs to be updated when a new environment account is onboarded.

Alternative (apex zone per account):

acme.com

Environment isolation is provided entirely by the account boundary. No env qualifier appears in DNS.

Summary

Prefix ConventionDNS Convention
{org}/{domain}/{service}{service}.{env}.{org}.com
Hierarchy grows left → rightHierarchy grows right → left
Namespace via accountNamespace via env subdomain
Logical identity preservedLogical identity preserved

DNS is not an exception to the naming convention — it is the same hierarchy represented in reverse due to DNS delegation design.


AWS SSM Parameter Store

SSM Parameter Store uses / as a native path delimiter and supports GetParametersByPath to fetch all parameters under a given prefix. Use it directly as the segment delimiter — it is the hierarchical naming convention with no translation required.

/{org}/{domain}/{service}/{key}
/acme/payments/checkout-api/stripe-webhook-secret

Every prefix is a valid, queryable operational boundary:

PathMeaning
/acme/*All parameters for the org
/acme/payments/*All parameters owned by payments
/acme/payments/checkout-api/*All parameters for a specific service
/acme/payments/checkout-api/stripe-webhook-secretA specific value
tip

IAM policies can scope access using path prefixes. A role for checkout-api can be granted access to only /acme/payments/checkout-api/*, with no wildcard bleed into sibling services or domains.

If the Parameter Store is not account-segregated by environment, add {env} after {org}:

/{org}/{env}/{domain}/{service}/{key}
/acme/prod/payments/checkout-api/stripe-webhook-secret

AWS S3 Object Keys

S3 object keys use / as a logical prefix delimiter. ListObjectsV2 accepts a Prefix parameter, making it efficient to list or process objects scoped to any segment boundary.

For most resources (e.g. application artefacts, backups), the standard hierarchy applies:

{org}/{domain}/{service}/{key}
acme/payments/checkout-api/schema-v3.sql

When storing high-volume data such as logs or events — where output is continuous and needs to be queried or processed in discrete chunks — add {partition} before {key}:

{org}/{domain}/{service}/{partition}/{key}
acme/payments/checkout-api/2024/01/15/14/transactions-00001.json

In this context {key} is the filename — the same segment, just a different form. {partition} groups many such {key} values into a queryable set.

{partition} is the only segment permitted to contain internal hierarchical delimiters. This allows a partition to span multiple levels of granularity within a single logical segment — for example, time-series log data is commonly partitioned as {year}/{month}/{day}/{hour}. Every sub-level of the partition remains a valid prefix boundary. All other segments must be flat.

Prefix boundaries remain meaningful at every level, including within the partition itself:

PrefixScope
acme/All objects for the org
acme/payments/All objects owned by payments
acme/payments/checkout-api/All objects for a specific service
acme/payments/checkout-api/2024/All log data for a given year
acme/payments/checkout-api/2024/01/All log data for a given month
acme/payments/checkout-api/2024/01/15/All log data for a given day
acme/payments/checkout-api/2024/01/15/14/All log data for a specific hour — download or replay that time window
acme/payments/checkout-api/2024/01/15/14/transactions-00001.jsonA specific log file
note

S3 bucket names are globally unique and require {env} and often {region} in the bucket name itself (see Concrete Examples above). But within the bucket, object key prefixes follow the standard hierarchy without {env} — the bucket name is already the environment boundary.


AWS IAM Paths

IAM roles, users, groups, and policies support an optional /path/ prefix that can be used to organise resources and scope access via IAM policy conditions (iam:ResourceTag or path-based conditions).

/{org}/{domain}/{service}/
/acme/payments/checkout-api/

A policy granting cross-service access to everything under payments:

"Resource": "arn:aws:iam::*:role/acme/payments/*"
IAM PathScope
/acme/*All roles in the org
/acme/payments/*All roles owned by payments
/acme/payments/checkout-api/*All roles for a specific service

Container Image Registries

Container registries such as ECR, Docker Hub, and GHCR use a {registry}/{namespace}/{image}:{tag} structure. Map org and domain onto the namespace hierarchy, with the image tag serving as {key}:

{registry}/{org}/{domain}/{service}:{key}

The : is the native delimiter for the tag in image references — the same role / plays in path hierarchies. {key} here is the image tag (e.g. 1.2.3): the final identifier that specifies exactly which artifact is being pulled.

Examples:

RegistryExample
ECR123456789.dkr.ecr.ap-southeast-2.amazonaws.com/acme/payments/checkout-api:1.2.3
Docker Hubdocker.io/acme/checkout-api:1.2.3
GHCRghcr.io/acme/checkout-api:1.2.3

{key} encodes the version, not the environment. Environment is determined by which account pulls and runs the image, not by the image reference itself.

Cross cutting secrets/config

info

Sometimes there will be cross cutting secrets/config which do not belong to any one particular domain, service or organization. Such as if the organization has purchased a service to be used by 2 different orgs. If this configuration cannot be definitively assigned to one or the other, then it should be stored in a cross cutting location, and permissions will need to be explicitly granted, rather than relying on prefix conventions.

Logical Name vs Physical Name

Logical Name: checkout-api

Physical Deployment Instance: Account: prod Region: ap-southeast-2 Name: checkout-api

Fully Qualified Identity: ap-southeast-2/prod/checkout-api

Name remains constant. Namespace varies.

Deployment Instance

A Deployment Instance is a concrete runtime instantiation of a logical resource within a specific namespace.

Example:

Logical Resource: checkout-api

Deployment Instances:

  • Account: dev → checkout-api
  • Account: prod → checkout-api
  • Account: uat → checkout-api

All share the same logical name but exist in different namespaces.

Naming Cheatsheet for best Pracices

· 16 min read
Derrick Futschik (Derrops)
Head of Engineering, Fortiro

Quick reference guide for naming AWS resources following the Derrops conventions. All examples assume account-segregated environments (preferred approach).

Core Format: {org}--{domain}--{service}--{key} (compound kebab-case with -- segment delimiters)


Template Variables Reference

Before using this cheatsheet, understand what each placeholder means:

VariableDefinitionExampleRequired?Notes
{region}AWS region codeap-southeast-2, us-east-1, eu-west-1✅ Only for globally unique services (S3, CloudFront, ACM, Route53)Omit if using account-per-region segregation
{env}Deployment environmentprod, dev, staging, uat✅ Only for globally unique services (S3) or DNSOmit if using account-per-environment segregation (recommended)
{org}Organization/top-level business unitacme, mycompany, client-name✅ Always requiredMost stable segment; rarely changes
{domain}Business capability / bounded domainpayments, identity, analytics, platform✅ Always requiredOwned independently; more stable than teams
{service}Deployable service unitcheckout-api, auth-service, webhook-worker✅ Always requiredThe primary identity; can be renamed/refactored
{key}Specific resource or config within servicetransactions, webhook-secret, primary, cache✅ Always requiredPurpose-specific identifier; changes frequently
{partition}Data partition grouping (logs, events only)2024/01/15/14, 2024-01-15❌ Optional; data storage onlyOnly for time-series or partitioned data in S3/Glue
{purpose}Functional purposealb, db, lambda, encryption-enabled✅ When needed for clarityQualifies the resource type
{type}Resource subtypeweb, worker, private, public, primary, replica✅ When distinguishing variantsMakes specific instances identifiable
{az}Availability zone1a, 1b, 1c✅ For multi-AZ resourcesEnsures subnets are distributed
{consumer}API consumer/clientmobile-client, partner-integrations, internal✅ For API keys and accessIdentifies who consumes the resource
{target}Target system for data sourcedynamodb, rds, lambda, s3✅ For integration pointsWhat the resource connects to
{num}Sequential number01, 02, 03❌ Optional; for instance namingZero-padded for sorting
{yyyy}/{mm}/{dd}/{hh}Date/time partitions2024/01/15/14✅ For time-series data onlyEach level is independently queryable
{file}Filenametransactions.json, logs.parquet✅ For object/file storageThe final artifact identifier
{version} or {tag}Semantic version or release tag1.2.3, latest, v2.0.0-beta✅ For images and artifactsIdentifies specific release
{registry}Container registry host123456789.dkr.ecr.ap-southeast-2.amazonaws.com, docker.io✅ For container imagesWhere the image is hosted

Summary Table - All Services

ServiceFormatPatternDelimiterExample
S3 BucketGlobal + prefixap-southeast-2--prod--{org}--{domain}--{service}--{key}-ap-southeast-2--prod--acme--payments--checkout-api--backups
S3 Object KeysHierarchy{org}/{domain}/{service}/{key}/acme/payments/checkout-api/schema.sql
S3 Logs/EventsWith partition{org}/{domain}/{service}/{yyyy}/{mm}/{dd}/{hh}/{file}/acme/payments/checkout-api/2024/01/15/14/transactions-00001.json
CloudWatch LogsHierarchy/{org}/{domain}/{service}/{key}//acme/payments/checkout-api/application-logs
CloudWatch Metrics (Namespace)Hierarchy (org/domain only){org}/{domain}/acme/payments
CloudWatch Metric (Dimensions)Key-value pairsservice={service} (env via account, not dimension)N/Aservice=checkout-api, service=order-processor
CloudWatch Metric NamesFlat kebab{key}-{metric-type}-request-count, error-rate, latency-p99
ECS ClusterFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--cluster
ECS ServiceFlat kebab{org}--{domain}--{service}-- / -acme--payments--checkout-api
ECS Task DefinitionFlat kebab{org}--{domain}--{service}-- / -acme--payments--checkout-api
ECR RepositoryRegistry path{org}/{domain}/{service}/acme/payments/checkout-api
ECR Image TagSemantic{registry}/{org}/{domain}/{service}:{version}/ :123456789.dkr.ecr.ap-southeast-2.amazonaws.com/acme/payments/checkout-api:1.2.3
DynamoDB TableFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--transactions
DynamoDB GSIFlat kebab{key}--gsi-- / -transactions-by-user--gsi
RDS Instance IDFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--primary
RDS DB NameFlat snake{org}_{domain}_{service}_acme_payments_checkout_api
RDS Parameter GroupFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--params
RDS Subnet GroupFlat kebab{org}--{domain}--{service}--subnet-group-- / -acme--payments--checkout-api--subnet-group
EC2 InstanceFlat kebab{org}--{domain}--{service}--{type}-{num}-- / -acme--payments--checkout-api--web-01
EC2 Security GroupFlat kebab{org}--{domain}--{service}--{purpose}-- / -acme--payments--checkout-api--alb
EC2 VolumeFlat kebab{org}--{domain}--{service}--volume-{purpose}-- / -acme--payments--checkout-api--volume-data
EC2 Elastic IPFlat kebab{org}--{domain}--{service}--eip-- / -acme--payments--checkout-api--eip
Lambda FunctionFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--webhook-handler
Lambda LayerFlat kebab{org}--{domain}--{service}--{purpose}-- / -acme--shared-utilities--common-libs
Lambda AliasSimpleprod, dev, stagingN/Aprod
IAM RolePath + namePath: /{org}/{domain}/{service}/ Name: {service}--{purpose}-role/ -- -ARN: arn:aws:iam::123456789:role/acme/payments/checkout-api/checkout-api--lambda-role
IAM PolicyPath + namePath: /{org}/{domain}/{service}/ Name: {purpose}-policy/ -acme--payments--checkout-api--s3-access-policy
IAM UserFlat kebab{org}--{domain}--{service}--user-- / -acme--payments--checkout-api--service-user
Route53 Hosted ZoneSubdomainprod.acme.com (env from account).prod.acme.com
Route53 DNS RecordReverse hierarchy{service}.prod.acme.com.checkout-api.prod.acme.com
Route53 Private ZoneInternal DNS{service}.internal.prod.acme.com.checkout-api.internal.prod.acme.com
CloudFront DistributionFlat kebab{org}--{domain}--{service}--cdn-- / -acme--payments--checkout-api--cdn
CloudFront Alias (CNAME)DNS pattern{service}.prod.acme.com.checkout-api.prod.acme.com
ACM Certificate DomainDNS pattern{service}.prod.acme.com.checkout-api.prod.acme.com
ACM Wildcard CertDNS wildcard*.prod.acme.com.*.prod.acme.com
VPCFlat kebab{org}--{domain}--{service}--vpc-- / -acme--payments--checkout-api--vpc
SubnetFlat kebab{org}--{domain}--{service}--subnet-{type}-{az}-- / -acme--payments--checkout-api--subnet-private-1a
Route TableFlat kebab{org}--{domain}--{service}--rt-{type}-- / -acme--payments--checkout-api--rt-private
Network ACLFlat kebab{org}--{domain}--{service}--nacl-- / -acme--payments--checkout-api--nacl
ALB/NLBFlat kebab{org}--{domain}--{service}--alb-- / -acme--payments--checkout-api--alb
Target GroupFlat kebab{org}--{domain}--{service}--tg-{purpose}-- / -acme--payments--checkout-api--tg-api
SNS TopicFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--transactions
SQS QueueFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--events
SQS FIFO QueueFlat kebab{org}--{domain}--{service}--{key}.fifo-- / -acme--payments--checkout-api--events.fifo
SQS DLQFlat kebab{queue-name}--dlq-- / -acme--payments--checkout-api--events--dlq
Kinesis StreamFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--events
EventBridge BusFlat kebab{org}--{domain}--{service}--events-- / -acme--payments--checkout-api--events
EventBridge RuleFlat kebab{org}--{domain}--{service}--{key}-rule-- / -acme--payments--checkout-api--process-webhook-rule
Step FunctionsFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--order-processing
API Gateway REST APIFlat kebab{org}--{domain}--{service}--api-- / -acme--payments--checkout-api--api
API Gateway HTTP APIFlat kebab{org}--{domain}--{service}--http-api-- / -acme--payments--checkout-api--http-api
API Gateway StageSimpleprod, dev, stagingN/Aprod
API Gateway KeyFlat kebab{org}--{domain}--{service}--{consumer}-- / -acme--payments--checkout-api--mobile-client
AppSync APIFlat kebab{org}--{domain}--{service}--api-- / -acme--payments--checkout-api--api
AppSync Data SourceFlat kebab{org}--{domain}--{service}--{target}-- / -acme--payments--checkout-api--dynamodb
ElastiCache ClusterFlat kebab{org}--{domain}--{service}--cache-- / -acme--payments--checkout-api--cache
ElastiCache Replication GroupFlat kebab{org}--{domain}--{service}--replication-group-- / -acme--payments--checkout-api--replication-group
ElastiCache Parameter GroupFlat kebab{org}--{domain}--{service}--params-- / -acme--payments--checkout-api--params
OpenSearch DomainFlat kebab{org}--{domain}--{service}-- / -acme--payments--checkout-api
OpenSearch IndexHierarchy{org}/{domain}/{service}/{key}/{date}/acme/payments/checkout-api/transactions/2024-01-15
RDS ProxyFlat kebab{org}--{domain}--{service}--proxy-- / -acme--payments--checkout-api--proxy
AWS Backup PlanFlat kebab{org}--{domain}--{service}--backup-plan-- / -acme--payments--checkout-api--backup-plan
AWS Backup VaultFlat kebab{org}--{domain}--{service}--vault-- / -acme--payments--checkout-api--vault
Glue DatabaseFlat snake{org}_{domain}_{service}_acme_payments_checkout_api
Glue TableFlat snake{key}N/Atransactions
Glue JobFlat kebab{org}--{domain}--{service}--{key}-job-- / -acme--analytics--etl--transform-job
Glue CrawlerFlat kebab{org}--{domain}--{service}--{key}-crawler-- / -acme--analytics--data-crawlers
Athena WorkgroupFlat kebab{org}--{domain}--{service}--workgroup-- / -acme--analytics--etl--workgroup
Athena Results BucketS3 key paths3://bucket/{org}/{domain}/{service}//s3://acme-analytics-athena-results/acme/analytics/etl/
QuickSight DatasetFlat kebab{org}--{domain}--{service}--dataset-- / -acme--analytics--transactions--dataset
QuickSight AnalysisFlat kebab{org}--{domain}--{service}--{key}-analysis-- / -acme--analytics--revenue-dashboard--analysis
QuickSight DashboardFlat kebab{org}--{domain}--{service}--{key}-- / -acme--analytics--revenue-dashboard
Redshift ClusterFlat kebab{org}--{domain}--{service}--cluster-- / -acme--analytics--warehouse--cluster
Redshift DatabaseFlat snake{org}_{domain}_{service}_acme_analytics_warehouse
Redshift Subnet GroupFlat kebab{org}--{domain}--{service}--subnet-group-- / -acme--analytics--warehouse--subnet-group
MSK ClusterFlat kebab{org}--{domain}--{service}--cluster-- / -acme--events--streaming--cluster
Kafka TopicDotted path{org}.{domain}.{service}.{key}.acme.payments.checkout-api.transactions
AppConfig ApplicationFlat kebab{org}--{domain}--{service}-- / -acme--payments--checkout-api
AppConfig EnvironmentSimpleprod, dev, stagingN/Aprod
AppConfig ProfileFlat kebab{org}--{domain}--{service}--{key}-profile-- / -acme--payments--checkout-api--feature-flags-profile
Systems Manager DocumentFlat kebab{org}--{domain}--{service}--{key}-- / -acme--payments--checkout-api--runbook
Systems Manager Maintenance WindowFlat kebab{org}--{domain}--{service}--maintenance-- / -acme--payments--checkout-api--patching-window
Service Catalog PortfolioFlat kebab{org}--{domain}--portfolio-- / -acme--payments--portfolio
Service Catalog ProductFlat kebab{org}--{domain}--{service}--product-- / -acme--payments--checkout-api--product
X-Ray Sampling RuleFlat kebab{org}--{domain}--{service}--sampling-rule-- / -acme--payments--checkout-api--sampling-rule
Config RuleFlat kebab{org}--{domain}--{service}--{key}-rule-- / -acme--payments--checkout-api--encryption-enabled-rule
Config AggregatorFlat kebab{org}--{domain}--config-aggregator-- / -acme--payments--config-aggregator
Security Hub Custom InsightFlat kebab{org}--{domain}--{service}--{key}-insight-- / -acme--payments--checkout-api--critical-findings-insight
WAF Web ACLFlat kebab{org}--{domain}--{service}--waf-- / -acme--payments--checkout-api--waf
WAF IP SetFlat kebab{org}--{domain}--{service}--{purpose}-ipset-- / -acme--payments--checkout-api--blocked-ips
WAF Rule GroupFlat kebab{org}--{domain}--{service}--rules-- / -acme--payments--checkout-api--rate-limit-rules
CloudFormation StackFlat kebab{org}--{domain}--{service}--{key}-stack-- / -acme--payments--checkout-api--stack
SSM ParameterHierarchy/{org}/{domain}/{service}/{key}//acme/payments/checkout-api/stripe-webhook-secret
Secrets Manager SecretHierarchy{org}/{domain}/{service}/{key}/acme/payments/checkout-api/db-password
Auto Scaling GroupFlat kebab{org}--{domain}--{service}--asg-- / -acme--payments--checkout-api--asg
Launch TemplateFlat kebab{org}--{domain}--{service}--launch-template-- / -acme--payments--checkout-api--launch-template

⚠️ Critical: Native Hierarchy Detection

Many services support their own native hierarchical constructs. Always check if a service has native hierarchy support BEFORE applying -- segment delimiters. Using native hierarchies enables:

  • Prefix filtering and querying
  • Permission scoping via path-based policies
  • Better operational organization
  • Automatic drill-down capabilities in console

Example: CloudWatch metrics require careful structure:

  • Namespace uses / for org/domain ONLY (e.g., acme/payments) ← USE THIS FOR HIERARCHY
  • Dimensions include service={service} (e.g., service=checkout-api); env via account boundary ← USE THIS FOR CROSS-SERVICE QUERIES
  • Metric names use - for words only (e.g., request-count, error-rate) ← Use this for specifics

Why? This enables meaningful queries like "all services in payments domain with high CPU" by filtering the service dimension, not namespaces. Each account's metrics are naturally isolated, maintaining permission boundaries while maximizing query utility!


Delimiter Decision Matrix

Use CaseDefault DelimiterNotesWhen to OverrideServices
Segment separator (org↔domain↔service↔key)-- (double hyphen)Separates major naming segments; most readableALWAYS check for native hierarchy firstAll flat resource names
Word within segment- (hyphen)Words/parts within a single segmentNever—always use - for wordsAll resource names
Path hierarchy (native)/ (slash)Native hierarchical support—use instead of --Use / instead of -- when availableS3 keys, SSM Parameters, Secrets, IAM paths, ECR, CloudWatch Logs, CloudWatch Metrics namespaces
DNS hierarchy (native). (dot)Native DNS subdomain separation—use instead of --Use . instead of -- when availableRoute53, DNS records, CloudFront aliases, Kafka topics
Image tag/version (native): (colon)Native registry delimiter for versioningUse : after image nameECR, Docker registries
Database internal names_ (underscore)DB-friendly; only for internal schema names, NOT identifiersUse _ instead of - for DB/schema names onlyRDS database names, Glue databases

Global vs Regional Scope

Resource TypeScopeIncludes region?Includes env?Example
S3 BucketsGlobally unique✅ Yes (ap-southeast-2)✅ Yes (prod)ap-southeast-2--prod--acme--payments--checkout-api--backups
Route53 Hosted ZonesGlobal DNS❌ No✅ Via account (prod.acme.com)prod.acme.com
CloudFront DistributionsGlobal CDN❌ No✅ Via DNS (prod)checkout-api.prod.acme.com
ACM CertificatesGlobal DNS❌ No✅ Via DNS (prod)checkout-api.prod.acme.com
IAM RolesGlobal (within account)❌ No❌ No (via account)/acme/payments/checkout-api/checkout-api-role
All Regional ServicesRegional❌ No (via account)❌ No (via account)acme-payments-checkout-api

Native Hierarchy Support

PRIORITY: Always use native hierarchies when available. They provide operational benefits flat names cannot.

ServiceNative SupportDelimiterExampleBenefit
S3 Object Keys✅ YES/acme/payments/checkout-api/schema.sqlPrefix filtering, drill-down in console
SSM Parameter Store✅ YES//acme/payments/checkout-api/stripe-keyGetParametersByPath queries, IAM scoping
Secrets Manager✅ YES/acme/payments/checkout-api/db-passwordPrefix filtering, organized in console
IAM Paths✅ YES//acme/payments/checkout-api/Path-based IAM policies, permission scoping
ECR Repositories✅ YES/acme/payments/checkout-apiNamespace organization in console
CloudWatch Logs✅ YES//acme/payments/checkout-api/logsLog group filtering and organization
CloudWatch Metrics (Namespace)✅ YES/ (org/domain only)acme/paymentsNamespace filtering; service as dimension enables cross-service queries
OpenSearch Indices✅ YES/acme/payments/checkout-api/transactions/2024-01-15Index pattern matching, time-series organization
Route53 DNS✅ YES.checkout-api.payments.acme.comDNS delegation, zone scoping
Kafka Topics✅ YES.acme.payments.checkout-api.eventsTopic organization, consumer group scoping
DynamoDB Tables❌ NO-Use {org}--{domain}--{service}--{key}No hierarchy support; use -- delimiters
RDS Instances❌ NO-Use {org}--{domain}--{service}--{key}No hierarchy support; use -- delimiters
Lambda Functions❌ NO-Use {org}--{domain}--{service}--{key}No hierarchy support; use -- delimiters
ECS/EC2 Resources❌ NO-Use {org}--{domain}--{service}--{key}No hierarchy support; use -- delimiters

Common Pitfalls & Solutions

PitfallProblemWhy It MattersSolution
Using _ in S3 bucket namesS3 rejects underscoresS3 bucket naming constraintUse - (hyphens) everywhere
Inconsistent names across environmentsCannot query resources across envsBreaks filtering in CloudWatch, Config, Security HubUse identical logical names in all accounts
Including {env} when account-segregatedRedundant naming; violates consistency principleNames become longer, harder to read; breaks queriesOmit {env} if managing via account boundaries
Randomly suffixed namesNames become unpredictableMakes automation fragile; breaks IaCUse account/region namespace for uniqueness
Changing {org} or {domain}Breaks all downstream references and policiesAll IAM policies, CloudWatch filters, and automation failKeep these segments stable; only change {service}
Not using native delimitersLoses prefix querying capabilityCannot use S3 prefix filtering, SSM GetParametersByPathUse / for hierarchical systems, . for DNS
DNS names don't mirror resourcesRouting confusion; cross-team coordination failureApplications cannot find correct endpointsDNS = reversed hierarchy (service.domain.org.com)
Resource name > service character limitTruncation breaks conventionNames get auto-truncated; cannot predict final nameTest limits early; use shorter domain/service names
Mixed kebab and snake caseTools cannot parse consistentlyScripts fail; team confusion; automation breaksUse kebab-case (-) for all naming except DB internals
Forgetting tagging for cost allocationCannot allocate costs accuratelyWrong cost attribution; misleading cost reportsTag every resource with CostCenter, Owner, Service

Quick Reference by Layer

Infrastructure Layer

VPC: acme--payments--checkout-api--vpc
Subnet: acme--payments--checkout-api--subnet-private-1a
Security Group: acme--payments--checkout-api--alb

Compute Layer

ECS Cluster: acme--payments--checkout-api--cluster
ECS Service: acme--payments--checkout-api
EC2 Instance: acme--payments--checkout-api--web-01
Lambda: acme--payments--checkout-api--webhook-handler

Data Layer

DynamoDB: acme--payments--checkout-api--transactions
RDS Instance: acme--payments--checkout-api--primary
RDS Database: acme_payments_checkout_api
ElastiCache: acme--payments--checkout-api--cache
S3 Bucket: ap-southeast-2--prod--acme--payments--checkout-api--data

Messaging Layer

SNS Topic: acme--payments--checkout-api--transactions
SQS Queue: acme--payments--checkout-api--events
SQS DLQ: acme--payments--checkout-api--events--dlq
Kinesis Stream: acme--payments--checkout-api--events

Integration Layer

API Gateway: acme--payments--checkout-api--api
Step Functions: acme--payments--checkout-api--order-processing
EventBridge Rule: acme--payments--checkout-api--process-webhook-rule

Observability Layer

CloudWatch Logs: /acme/payments/checkout-api/application-logs
CloudWatch Metrics:
Namespace: acme/payments (org/domain only)
Dimensions: service=checkout-api (env via account boundary)
Metric Name: request-count, error-rate, latency-p99
Example Query: "All high-CPU services in payments" → Query namespace acme/payments, filter by service dimension
Permission Boundary: Account segregation; no env dimension needed
X-Ray Rule: acme--payments--checkout-api--sampling-rule
Config Rule: acme--payments--checkout-api--encryption-enabled-rule

Security Layer

IAM Role: /acme/payments/checkout-api/checkout-api--lambda-role
IAM Policy: acme--payments--checkout-api--s3-access-policy
WAF Web ACL: acme--payments--checkout-api--waf
ACM Certificate: checkout-api.prod.acme.com

DNS Layer

Hosted Zone: prod.acme.com
DNS Record: checkout-api.prod.acme.com
Route53 Private Zone: checkout-api.internal.prod.acme.com
CloudFront Alias: checkout-api.prod.acme.com

Storage/Config Layer

S3 Object Key: acme/payments/checkout-api/schema.sql
SSM Parameter: /acme/payments/checkout-api/stripe-webhook-secret
Secrets Manager: acme/payments/checkout-api/db-password

Tagging Strategy

Apply these tags to all resources for cost allocation and resource management:

Tag KeyValueExamplePurpose
orgOrganizationacmeTop-level ownership
domainBusiness domainpaymentsCapability boundary
serviceService namecheckout-apiDeployment unit
environmentDeployment stageprodOptional if account-segregated
ownerTeam/personpayments-teamResponsibility tracking
cost-centerCost allocationpayments-teamBilling attribution
backup-requiredbooleantrueBackup policy enforcement
terraformbooleantrueIaC management indicator

Implementation Checklist

  • Define segments: org, domain, service values
  • Account strategy: 1 per environment? (Recommended: yes)
  • Test naming: Create 1-2 sample resources in non-prod
  • Document exceptions: Route53 reverse hierarchy, DNS patterns
  • Create IAM policies: Use path prefixes for least privilege
  • Enable AWS Config: Enforce naming patterns automatically
  • Set up tagging: Apply tags to non-nameable resources
  • Create runbooks: How to find resources by naming convention
  • Train team: Share cheatsheet and examples
  • Monitor drift: Regular audits for non-compliant names

Format Comparison Examples

Use CasePrefix HierarchyDNS ReverseFlat KebabResult
Same service across systems/acme/payments/checkout-apicheckout-api.payments.acme.comacme-payments-checkout-api✅ All represent same logical resource
Different purposes/acme/payments/checkout-api/ordersN/Aacme-payments-checkout-api-orders✅ Additional scope via suffix
Env segregated/acme/prod/payments/checkout-apicheckout-api.prod.acme.com❌ Not used (handled via account)✅ Account provides namespace
With partition (logs)acme/payments/checkout-api/2024/01/15/logsN/AN/A✅ Hierarchical querying in S3

One-Liner Reference

Need to name a resource? Apply this logic in order:

  1. Does it support native hierarchy? → Use it (/ for paths, . for DNS)
  2. Is it globally unique (S3, ACM, CloudFront)? → Add ap-southeast-2--prod-- prefix (literal region and env values)
  3. Is it DNS-based? → Use reverse hierarchy: {service}.prod.acme.com (env in domain via account)
  4. Otherwise → Use format: {org}-{domain}-{service}-{key} with - delimiters, -- between segments
  5. Tag everything else that doesn't support naming

Derrops Guide to Config

· 3 min read
Derrick Futschik (Derrops)
Head of Engineering, Fortiro

This document defines where configuration and secrets live, why, and the rules governing each layer.
The goal is to make configuration explicit, auditable, secure, and predictable.


Where does configuration live?

Configuration is split into layers, each with a clear responsibility.
No single system should answer every question.

Configuration Authority Hierarchy

Each configuration value has exactly one authority.

AuthoritySystem / LayerResponsibilityExample
Local AuthorityEnv File (.env file)Local dev enablementAllowed regions, schemas
Policy AuthorityGit (repository)What is allowedAllowed regions, schemas
Runtime AuthoritySSM Parameter StoreDefines what is activeFeature flags, live endpoints
Secret AuthoritySecrets ManagerDefines secret valuesAPI keys, passwords
Sensitive Config AuthoritySecureStringManages sensitive configEncrypted DB URI
Validation AuthorityCode schema validationEnsures correctnessZod/Valibot/Yup schema checks
Consumption AuthorityApplication codeConsumes configReads config object in code

.env files (Local Development Layer)

.env files are used only for local development.

Purpose

  • Provide secrets and configuration for local execution
  • Allow developers to run services without cloud dependency

Rules

  • Must not be committed to Git
  • Must be gitignored
  • Must only exist locally
  • Must not be used in production
  • Must follow the same schema as production configuration

Git (Policy Layer)

Git is the source of truth for intent and policy.

It answers why something exists, not what it currently is.

Git defines

  • Allowed values
  • Schemas
  • Constraints
  • Policy-level defaults
  • Environment shapes

Git must not contain

  • Secrets
  • Runtime values
  • Environment-specific endpoints
  • Credentials

Rules

  • All changes require review
  • All changes must be auditable
  • Git defines policy, not runtime state

SSM Parameter Store (Runtime Configuration Layer)

SSM represents runtime configuration state.

Rules

  • Values must conform to Git policy
  • Values must be environment-scoped
  • Changes must not require rebuilds
  • Parameters must be hierarchical and namespaced
  • Secrets Manager should be preferred over SSM for secrets

Naming Convention

/{org}/{system}/{env}/{service}/{key}

Example:

/fortiro/protect/prod/api/feature/scan-enabled /fortiro/protect/prod/api/db/host


Secrets Manager (Primary Secrets Layer)

Secrets Manager is the preferred system for managing secrets.

Rules

  • Must be injected at runtime
  • Must never be hardcoded
  • Must never be committed to Git
  • Must be readable only by authorised services
  • Rotation should be enabled when possible

Tags (Metadata Layer)

Tags define ownership and metadata.

Rules

  • All AWS resources must be tagged
  • Tags must never control application behavior
  • Tags must not contain secrets

Codebase (Consumer Layer)

The codebase consumes configuration.

Rules

  • Must not define runtime values
  • Must not define secrets
  • Must validate schema
  • Must fail fast on missing configuration

Fail Fast Requirement

Application startup must fail if:

  • Required configuration is missing
  • Secrets cannot be retrieved
  • Configuration violates schema

Configuration Injection Model

Configuration must be injected at runtime via:

  • Environment variables
  • SSM Parameter Store
  • Secrets Manager

Configuration must never be compiled into the application artifact.


Runtime Defaults Policy

Defaults must exist only in configuration, not application logic.

Allowed:

size: params.size ?? config['app.pagination.default.size']

Forbidden:

size: params.size ?? 20


Environment Precedence

Highest precedence first:

  1. Explicit test configuration injection
  2. System environment variables
  3. Local .env
  4. SSM Parameter Store
  5. Secrets Manager
  6. Git policy
  7. Code schema validation

Result

This model ensures:

  • Secure secret handling
  • Explicit runtime configuration
  • Fail-fast startup guarantees
  • Predictable deployments

Glossary

  • Deployment Instances