Skip to main content
  1. Docs
  2. Infrastructure as Code
  3. Operations
  4. Stack Management
  5. Refactoring with aliases

Refactoring with aliases

    Pulumi identifies every resource by its URN, which encodes the resource’s name, type, parent path, project, and stack. When you change any of those — by renaming a resource, moving it under a new parent, wrapping a group of resources in a ComponentResource, or migrating to a different provider type — the URN changes. By default Pulumi will treat the old URN as a delete and the new URN as a create, even though the underlying cloud resource is the same.

    The aliases resource option tells Pulumi “this old URN refers to the same resource as the new one.” With it, you can restructure a program without forcing replacement on real infrastructure.

    This page focuses on the operational workflow — when to reach for an alias and how to remove it once the migration is complete. For the full property reference and every combination of fields on the Alias type, see the aliases option reference.

    When you need an alias

    You’re changingUse an alias entry likeSection
    The logical name passed to the resource constructor{ name: "old-name" }Renaming a resource
    The parent resource option{ parent: oldParent }Re-parenting a resource
    A group of resources, by wrapping them in a new component{ parent: <root-stack> } on each childWrapping resources in a component
    The resource’s type token{ type: "old:Type" }Changing a resource’s type
    The stack or project the resource belongs to{ stack: "old-stack", project: "old-project" }Moving across stacks or projects

    You never need an alias when changing a property inside the resource’s args (size, region, tags, and so on). Property changes are reconciled normally; only changes that affect the URN need an alias.

    Before any of these refactors, run pulumi preview on each stack to confirm the alias prevents replacement. The preview should show the resource as same (or as an in-place update), not delete + create.

    Renaming a resource

    Pass the old name in an alias entry. The first pulumi up after the change updates the stack state to use the new URN; no cloud resource is touched.

    const db = new Database("primary-db", { /* ... */ }, {
        aliases: [{ name: "old-db" }],
    });
    
    db = Database("primary-db",
        opts=ResourceOptions(aliases=[Alias(name="old-db")]))
    
    db, err := NewDatabase(ctx, "primary-db", &DatabaseArgs{ /* ... */ },
        pulumi.Aliases([]pulumi.Alias{
            {Name: pulumi.String("old-db")},
        }))
    
    var db = new Database("primary-db", new DatabaseArgs(),
        new CustomResourceOptions { Aliases = { new Alias { Name = "old-db" } } });
    
    var db = new Database("primary-db",
        DatabaseArgs.Empty,
        CustomResourceOptions.builder()
            .aliases(Alias.builder().name("old-db").build())
            .build());
    
    resources:
      primary-db:
        type: pkg:index:Database
        options:
          aliases:
            - name: old-db
    

    Re-parenting a resource

    When a resource moves under a new explicit parent, reference the old parent in the alias. Pulumi will recognize the resource by its previous URN and update the parent path in state.

    const key = new ServiceAccountKey("primary-key", {
        serviceAccountId: newParent.email,
    }, {
        parent: newParent,
        aliases: [{ parent: oldParent }],
    });
    
    key = ServiceAccountKey("primary-key",
        service_account_id=new_parent.email,
        opts=ResourceOptions(
            parent=new_parent,
            aliases=[Alias(parent=old_parent)],
        ))
    
    key, err := NewServiceAccountKey(ctx, "primary-key", &ServiceAccountKeyArgs{
        ServiceAccountId: newParent.Email,
    }, pulumi.Parent(newParent), pulumi.Aliases([]pulumi.Alias{
        {Parent: oldParent},
    }))
    
    var key = new ServiceAccountKey("primary-key", new ServiceAccountKeyArgs
    {
        ServiceAccountId = newParent.Email,
    }, new CustomResourceOptions
    {
        Parent = newParent,
        Aliases = { new Alias { Parent = oldParent } },
    });
    
    var key = new ServiceAccountKey("primary-key",
        ServiceAccountKeyArgs.builder()
            .serviceAccountId(newParent.email())
            .build(),
        CustomResourceOptions.builder()
            .parent(newParent)
            .aliases(Alias.builder().parent(oldParent).build())
            .build());
    
    resources:
      primary-key:
        type: gcp:serviceAccount:Key
        properties:
          serviceAccountId: ${newParent.email}
        options:
          parent: ${newParent}
          aliases:
            - parent: ${oldParent}
    

    Aliases are inherited from a parent, so if the parent itself is also being renamed or re-parented, you don’t need to repeat that information on each child. See the reference page for the inheritance rules.

    Wrapping resources in a component

    A common refactor is to take a handful of related resources that were declared at the stack root and group them inside a new ComponentResource. Each child needs an alias indicating it previously lived at the stack root (no explicit parent). The sentinel differs per SDK: TypeScript and Python have a named root-stack value, while Go, .NET, and Java use a NoParent flag with equivalent semantics.

    import * as pulumi from "@pulumi/pulumi";
    
    export class AppData extends pulumi.ComponentResource {
        constructor(name: string, args: AppDataArgs, opts?: pulumi.ComponentResourceOptions) {
            super("myorg:app:AppData", name, {}, opts);
    
            const db = new Database(`${name}-db`, {
                engine: args.engine,
            }, {
                parent: this,
                aliases: [{ parent: pulumi.rootStackResource }],
            });
    
            const cache = new Cache(`${name}-cache`, {
                sizeMb: args.cacheSizeMb,
            }, {
                parent: this,
                aliases: [{ parent: pulumi.rootStackResource }],
            });
    
            this.registerOutputs({});
        }
    }
    
    import pulumi
    
    class AppData(pulumi.ComponentResource):
        def __init__(self, name, args, opts=None):
            super().__init__("myorg:app:AppData", name, {}, opts)
    
            db = Database(f"{name}-db",
                engine=args.engine,
                opts=pulumi.ResourceOptions(
                    parent=self,
                    aliases=[pulumi.Alias(parent=pulumi.ROOT_STACK_RESOURCE)],
                ))
    
            cache = Cache(f"{name}-cache",
                size_mb=args.cache_size_mb,
                opts=pulumi.ResourceOptions(
                    parent=self,
                    aliases=[pulumi.Alias(parent=pulumi.ROOT_STACK_RESOURCE)],
                ))
    
            self.register_outputs({})
    
    type AppData struct {
        pulumi.ResourceState
    }
    
    func NewAppData(ctx *pulumi.Context, name string, args *AppDataArgs, opts ...pulumi.ResourceOption) (*AppData, error) {
        component := &AppData{}
        if err := ctx.RegisterComponentResource("myorg:app:AppData", name, component, opts...); err != nil {
            return nil, err
        }
    
        _, err := NewDatabase(ctx, name+"-db", &DatabaseArgs{
            Engine: args.Engine,
        }, pulumi.Parent(component), pulumi.Aliases([]pulumi.Alias{
            {NoParent: pulumi.Bool(true)},
        }))
        if err != nil {
            return nil, err
        }
    
        _, err = NewCache(ctx, name+"-cache", &CacheArgs{
            SizeMb: args.CacheSizeMb,
        }, pulumi.Parent(component), pulumi.Aliases([]pulumi.Alias{
            {NoParent: pulumi.Bool(true)},
        }))
        if err != nil {
            return nil, err
        }
    
        return component, nil
    }
    
    public class AppData : ComponentResource
    {
        public AppData(string name, AppDataArgs args, ComponentResourceOptions? opts = null)
            : base("myorg:app:AppData", name, opts)
        {
            var db = new Database($"{name}-db", new DatabaseArgs
            {
                Engine = args.Engine,
            }, new CustomResourceOptions
            {
                Parent = this,
                Aliases = { new Alias { NoParent = true } },
            });
    
            var cache = new Cache($"{name}-cache", new CacheArgs
            {
                SizeMb = args.CacheSizeMb,
            }, new CustomResourceOptions
            {
                Parent = this,
                Aliases = { new Alias { NoParent = true } },
            });
    
            RegisterOutputs();
        }
    }
    
    public class AppData extends ComponentResource {
        public AppData(String name, AppDataArgs args, ComponentResourceOptions opts) {
            super("myorg:app:AppData", name, opts);
    
            var db = new Database(name + "-db",
                DatabaseArgs.builder().engine(args.engine()).build(),
                CustomResourceOptions.builder()
                    .parent(this)
                    .aliases(Alias.noParent())
                    .build());
    
            var cache = new Cache(name + "-cache",
                CacheArgs.builder().sizeMb(args.cacheSizeMb()).build(),
                CustomResourceOptions.builder()
                    .parent(this)
                    .aliases(Alias.noParent())
                    .build());
    
            this.registerOutputs();
        }
    }
    

    In your program, replace the standalone Database and Cache declarations with a single new AppData(...) (or the language-equivalent constructor). After the next pulumi up, the resources move under the new component in state with no churn in the cloud.

    YAML programs don’t define component resources directly. To group resources in YAML, factor the group into a component package or move the program to one of the languages above.

    Changing a resource’s type

    When a provider deprecates a resource type or you migrate to a different provider entirely, the resource’s type token changes. Add the old type in an alias entry:

    const db = new aws.rds.Instance("primary-db", { /* ... */ }, {
        aliases: [{ type: "aws:rds/database:Database" }],
    });
    
    db = aws.rds.Instance("primary-db",
        opts=ResourceOptions(aliases=[Alias(type_="aws:rds/database:Database")]))
    
    db, err := rds.NewInstance(ctx, "primary-db", &rds.InstanceArgs{ /* ... */ },
        pulumi.Aliases([]pulumi.Alias{
            {Type: pulumi.String("aws:rds/database:Database")},
        }))
    
    var db = new Aws.Rds.Instance("primary-db", new Aws.Rds.InstanceArgs(),
        new CustomResourceOptions { Aliases = { new Alias { Type = "aws:rds/database:Database" } } });
    
    var db = new Instance("primary-db",
        InstanceArgs.Empty,
        CustomResourceOptions.builder()
            .aliases(Alias.builder().type("aws:rds/database:Database").build())
            .build());
    
    resources:
      primary-db:
        type: aws:rds:Instance
        options:
          aliases:
            - type: aws:rds/database:Database
    

    A type-change alias only convinces Pulumi that the URN refers to the same resource. The new resource’s schema must still be compatible with the cloud resource that exists — for example, the provider must use the same underlying API and accept the same identifiers. If the schemas are incompatible, use pulumi import instead.

    Moving across stacks or projects

    When a resource moves to a new stack name or project name, supply the old identity:

    const db = new Database("primary-db", { /* ... */ }, {
        aliases: [{ stack: "old-stack", project: "old-project" }],
    });
    
    db = Database("primary-db",
        opts=ResourceOptions(aliases=[Alias(stack="old-stack", project="old-project")]))
    
    db, err := NewDatabase(ctx, "primary-db", &DatabaseArgs{ /* ... */ },
        pulumi.Aliases([]pulumi.Alias{
            {Stack: pulumi.String("old-stack"), Project: pulumi.String("old-project")},
        }))
    
    var db = new Database("primary-db", new DatabaseArgs(),
        new CustomResourceOptions { Aliases = { new Alias { Stack = "old-stack", Project = "old-project" } } });
    
    var db = new Database("primary-db",
        DatabaseArgs.Empty,
        CustomResourceOptions.builder()
            .aliases(Alias.builder().stack("old-stack").project("old-project").build())
            .build());
    
    resources:
      primary-db:
        type: pkg:index:Database
        options:
          aliases:
            - stack: old-stack
              project: old-project
    

    This alias form covers two common scenarios:

    Renaming a stack or project. pulumi stack rename changes the stack’s name and updates URNs in its state automatically; renaming a project means editing name: in Pulumi.yaml. A stack or project alias entry is still useful here when your program embeds the old identifier into a resource name — most commonly via getStack() — because the resource’s URN changes even though the state-level rename already happened. The alias bridges that secondary change.

    Moving resources between stacks. Use pulumi state move to physically transfer resources from a source stack to a destination stack. The command rewrites the moved resources’ URNs to use the destination’s stack and project, so the move itself doesn’t require an alias. Add a stack and/or project alias on those resources in the destination’s program when the destination’s code declares them with a different identity than the source did, or when you migrated state through some other path (export/import, manual edits) and the URNs weren’t rewritten for you.

    Combining multiple alias entries

    An aliases list can hold more than one entry, and the meaning of “list of entries” differs from “one entry with multiple fields”:

    • aliases: [{ name: "old-name" }, { parent: oldParent }] — two alternative identities. Pulumi matches the resource if its previous URN had either the old name or the old parent. Use this when a resource changed name in one update and parent in another, and you don’t know which version of state the user is upgrading from.
    • aliases: [{ name: "old-name", parent: oldParent }] — a single alias requiring both fields to match. Use this when the resource simultaneously had the old name and the old parent in the previous program.

    When in doubt, prefer the list-of-objects form: it’s broader, and Pulumi will pick whichever entry matches. False positives on alias matches are not a risk in practice — URNs are unique, so at most one entry can match a given resource.

    Removing aliases after migration

    An alias is only load-bearing while at least one stack’s state still references the old URN. Once every stack of the project has run pulumi up (or any operation that writes state, such as pulumi refresh) against the program with the alias in place, the new URN is recorded in state and the alias does nothing.

    At that point you can delete the alias from the program:

    1. List every stack of the project: pulumi stack ls.
    2. For each stack, run pulumi preview. The resource should appear as same with no aliases warning in the output.
    3. Remove the aliases entry from the program.
    4. Run pulumi preview once more on each stack to confirm no replacement is planned.

    Leaving stale aliases in the program isn’t harmful, but they accumulate as code noise and obscure the intent of future refactors. Treat alias removal as a follow-up commit a few weeks after the original refactor lands, once you’re confident every stack has rolled forward.

    See also