1. Packages
  2. AWS Classic
  3. How-to Guides
  4. Deploy a Webserver to AWS EC2

Try AWS Native preview for resources not in the classic version.

AWS Classic v6.42.0 published on Wednesday, Jun 26, 2024 by Pulumi

Deploy a Webserver to AWS EC2

aws logo

Try AWS Native preview for resources not in the classic version.

AWS Classic v6.42.0 published on Wednesday, Jun 26, 2024 by Pulumi

    View TypeScript Code View JavaScript Code View Python Code View C# Code

    In this tutorial, we will show you how to deploy a simple webserver using an Amazon EC2 instance.

    Prerequisites

    1. Install Pulumi
    2. Configure AWS credentials
    3. Choose your language and install the required version

    Install Node.js.

    If you're having trouble setting up Node.js up on your machine, see Installing Node.js via Package Manager for alternative installation options.

    Install Python. To reduce potential issues with setting up your Python environment on Windows or macOS, you should install Python through the official Python installer.

    pip is required to install dependencies. If you installed Python from source, with an installer from python.org, or via Homebrew you should already have pip. If Python is installed using your OS package manager, you may have to install pip separately, see Installing pip/setuptools/wheel with Linux Package Managers. For example, on Debian/Ubuntu you must run sudo apt install python3-venv python3-pip.

    If you're having trouble setting up Python on your machine, see Python 3 Installation & Setup Guide for detailed installation instructions on various operating systems and distributions.

    Install .NET SDK.

    Pulumi will need the dotnet executable in order to build and run your Pulumi .NET application. Ensure that the dotnet executable can be found on your path after installation.

    Deploy the App

    Step 1: Create a new project from a template

    Create a project directory, webserver, and change into it. Run pulumi new aws-<language> --name myproject to create a new project using the AWS template for your chosen language. Replace myproject with your desired project name.

    $ mkdir webserver && cd webserver
    $ pulumi new aws-javascript --name myproject
    
    $ mkdir webserver && cd webserver
    $ pulumi new aws-typescript --name myproject
    
    $ mkdir webserver && cd webserver
    $ pulumi new aws-python --name myproject
    
    $ mkdir webserver && cd webserver
    $ pulumi new aws-csharp --name myproject
    

    Step 2: Create an EC2 instance with HTTP access

    Open index.js index.ts __main__.py main.go Program.cs Program.fs Program.vb App.java Pulumi.yaml and replace the contents with the following:

    const aws = require("@pulumi/aws");
    const pulumi = require("@pulumi/pulumi");
    
    let size = "t2.micro";     // t2.micro is available in the AWS free tier
    let ami = aws.ec2.getAmiOutput({
        filters: [{
          name: "name",
          values: ["amzn-ami-hvm-*"],
        }],
        owners: ["137112412989"], // This owner ID is Amazon
        mostRecent: true,
    });
    
    let group = new aws.ec2.SecurityGroup("webserver-secgrp", {
        ingress: [
            { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
        ],
    });
    
    let server = new aws.ec2.Instance("webserver-www", {
        instanceType: size,
        vpcSecurityGroupIds: [ group.id ], // reference the security group resource above
        ami: ami.id,
    });
    
    exports.publicIp = server.publicIp;
    exports.publicHostName = server.publicDns;
    
    import * as aws from "@pulumi/aws";
    import * as pulumi from "@pulumi/pulumi";
    
    const size = "t2.micro";     // t2.micro is available in the AWS free tier
    const ami = aws.ec2.getAmiOutput({
        filters: [{
            name: "name",
            values: ["amzn-ami-hvm-*"],
        }],
        owners: ["137112412989"], // This owner ID is Amazon
        mostRecent: true,
    });
    
    const group = new aws.ec2.SecurityGroup("webserver-secgrp", {
        ingress: [
            { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
        ],
    });
    
    const server = new aws.ec2.Instance("webserver-www", {
        instanceType: size,
        vpcSecurityGroupIds: [ group.id ], // reference the security group resource above
        ami: ami.id,
    });
    
    export const publicIp = server.publicIp;
    export const publicHostName = server.publicDns;
    
    import pulumi
    import pulumi_aws as aws
    
    size = 't2.micro'
    ami = aws.ec2.get_ami(most_recent="true",
                      owners=["137112412989"],
                      filters=[{"name":"name","values":["amzn-ami-hvm-*"]}])
    
    group = aws.ec2.SecurityGroup('webserver-secgrp',
        description='Enable HTTP access',
        ingress=[
            { 'protocol': 'tcp', 'from_port': 22, 'to_port': 22, 'cidr_blocks': ['0.0.0.0/0'] }
        ])
    
    server = aws.ec2.Instance('webserver-www',
        instance_type=size,
        vpc_security_group_ids=[group.id], # reference security group from above
        ami=ami.id)
    
    pulumi.export('publicIp', server.public_ip)
    pulumi.export('publicHostName', server.public_dns)
    
    using Pulumi;
    using Pulumi.Aws.Ec2;
    using Pulumi.Aws.Ec2.Inputs;
    
    return await Deployment.RunAsync(() =>
    {
        var ami = GetAmi.Invoke(new GetAmiInvokeArgs
        {
            Owners = { "137112412989" }, // This owner ID is Amazon
            MostRecent = true,
            Filters =
            {
                new GetAmiFilterInputArgs
                {
                    Name = "name",
                    Values =  { "amzn-ami-hvm-*" },
                },
            },
        });
    
        var group = new SecurityGroup("webserver-secgrp", new SecurityGroupArgs
        {
            Ingress = new SecurityGroupIngressArgs
            {
                Protocol = "tcp",
                FromPort = 22,
                ToPort = 22,
                CidrBlocks = { "0.0.0.0/0" }
            },
        });
    
        var userData = @"
                    #!/bin/bash
                    echo ""Hello, World!"" > index.html
                    nohup python -m SimpleHTTPServer 80 &
                    ";
    
        var server = new Instance("webserver-www", new InstanceArgs
        {
            // t2.micro is available in the AWS free tier
            InstanceType = "t2.micro",
            VpcSecurityGroupIds = { group.Id }, // reference the security group resource above
            UserData = userData,
            Ami = ami.Apply(x => x.Id),
        });
    
        return new Dictionary<string, object?>
        {
            ["publicIp"] = server.PublicIp,
            ["publicHostName"] = server.PublicDns
        };
    });
    

    Note: The example configuration is designed to work on most EC2 accounts, with access to a default VPC. For EC2 Classic users, please use t1.micro for size.

    This example uses the ec2 module of the aws package to create two resources:

    AWS ResourceDescriptionResource
    Security GroupCreated for allowing incoming HTTP accessaws.ec2.SecurityGroup
    EC2 InstanceCreated in that security group using the appropriate Amazon Machine Image (AMI) for the region where you deploy the programaws.ec2.Instance

    Step 3: Preview and deploy your resources

    To preview your Pulumi program, run pulumi up. The command shows a preview of the resources that will be created and prompts you to proceed with the deployment. Note that the stack itself is counted as a resource, though it does not correspond to a physical cloud resource.

    Previewing update (webserver-dev):
    
         Type                      Name                     Plan
     +   pulumi:pulumi:Stack       myproject-webserver-dev  create
     +   ├─ aws:ec2:SecurityGroup  webserver-secgrp         create
     +   └─ aws:ec2:Instance       webserver-www            create
    
    Resources:
        + 3 to create
    
    Do you want to perform this update?
      yes
    > no
      details
    

    Next, proceed with the deployment, which takes about 40 seconds to complete.

    Do you want to perform this update? yes
    Updating (webserver-dev):
    
         Type                      Name                     Status
     +   pulumi:pulumi:Stack       myproject-webserver-dev  created
     +   ├─ aws:ec2:SecurityGroup  webserver-secgrp         created
     +   └─ aws:ec2:Instance       webserver-www            created
    
    Outputs:
        publicHostName: "ec2-34-217-110-29.us-west-2.compute.amazonaws.com"
        publicIp      : "34.217.110.29"
    
    Resources:
        + 3 created
    
    Duration: 40s
    
    Permalink: https://app.pulumi.com/bermudezmt/myproject/webserver-dev/updates/1
    

    Step 4: View your stack resources

    Pulumi Cloud

    To see the full details of the deployment and the resources that are now part of the stack, open the update link in a browser. The Resources tab in the Pulumi Cloud has a link to the AWS console for the provisioned EC2 instance.

    Pulumi CLI

    To view the provisioned resources on the command line, run pulumi stack. You’ll also see two stack outputs corresponding to the IP and the fully qualified domain name (FQDN) of the EC2 instance we’ve created.

    Current stack is webserver-dev:
        Owner: <your-org-name>
        Last updated: 10 minutes ago (2019-09-20 11:57:55.90881794 -0700 PDT)
        Pulumi version: v1.1.0
    Current stack resources (4):
        TYPE                                 NAME
        pulumi:pulumi:Stack                  myproject-webserver-dev
        pulumi:providers:aws                 default_1_2_1
        aws:ec2/securityGroup:SecurityGroup  webserver-secgrp
        aws:ec2/instance:Instance            webserver-www
    
    More information at: https://app.pulumi.com/<your-org-name>/myproject/webserver-dev
    
    Use `pulumi stack select` to change stack; `pulumi stack ls` lists known ones
    

    Step 5: Update the Pulumi program

    Now that you have an instance of the Pulumi program deployed, you may want to make changes. You do so by updating the Pulumi program to define the new state you want your infrastructure to be in, and then running pulumi up to commit the changes.

    Replace the creation of the two resources with the following code. This exposes an additional port, 80, and adds a startup script to run a simple HTTP server at startup.

    ...
    
    let group = new aws.ec2.SecurityGroup("webserver-secgrp", {
        ingress: [
            { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
            { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
            // ^-- ADD THIS LINE
        ],
    });
    
    let userData = // <-- ADD THIS DEFINITION
    `#!/bin/bash
    echo "Hello, World!" > index.html
    nohup python -m SimpleHTTPServer 80 &`;
    
    let server = new aws.ec2.Instance("web-server-www", {
        instanceType: size,
        vpcSecurityGroupIds: [ group.id ], // reference the group object above
        ami: ami.id,
        userData: userData,             // <-- ADD THIS LINE
    });
    
    ...
    
    ...
    
    const group = new aws.ec2.SecurityGroup("webserver-secgrp", {
        ingress: [
            { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
            { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
            // ^-- ADD THIS LINE
        ],
    });
    
    const userData = // <-- ADD THIS DEFINITION
    `#!/bin/bash
    echo "Hello, World!" > index.html
    nohup python -m SimpleHTTPServer 80 &`;
    
    const server = new aws.ec2.Instance("webserver-www", {
        instanceType: size,
        vpcSecurityGroupIds: [ group.id ], // reference the security group resource above
        ami: ami.id,
        userData: userData,             // <-- ADD THIS LINE
    });
    
    ...
    
    ...
    
    group = aws.ec2.SecurityGroup('webserver-secgrp',
        description='Enable HTTP access',
        ingress=[
            { 'protocol': 'tcp', 'from_port': 22, 'to_port': 22, 'cidr_blocks': ['0.0.0.0/0'] },
            { 'protocol': 'tcp', 'from_port': 80, 'to_port': 80, 'cidr_blocks': ['0.0.0.0/0'] }
            # ^-- ADD THIS LINE
        ])
    
    user_data = """
    #!/bin/bash
    echo "Hello, World!" > index.html
    nohup python -m SimpleHTTPServer 80 &
    """
    # ^-- ADD THIS DEFINITION
    
    server = aws.ec2.Instance('webserver-www',
        instance_type=size,
        vpc_security_group_ids=[group.id], # reference security group from above
        user_data=user_data, # <-- ADD THIS LINE
        ami=ami.id)
    
    ...
    
    //...
    var group = new Aws.Ec2.SecurityGroup("webserver-secgrp", new Aws.Ec2.SecurityGroupArgs
    {
        Ingress =
        {
            new Aws.Ec2.SecurityGroupIngressArgs
            {
                Protocol = "tcp",
                FromPort = 22,
                ToPort = 22,
                CidrBlocks = { "0.0.0.0/0" },
            },
            new Aws.Ec2.SecurityGroupIngressArgs
            {
                Protocol = "tcp",
                FromPort = 80,
                ToPort = 80,
                CidrBlocks = { "0.0.0.0/0" },
            },
            // ^-- ADD THIS item
        },
    });
    
    var userData = // <-- ADD THIS DEFINITION
    @"#!/bin/bash
    echo ""Hello, World!"" > index.html
    nohup python -m SimpleHTTPServer 80 &";
    
    var server = new Aws.Ec2.Instance("webserver-www", new Aws.Ec2.InstanceArgs
    {
        // t2.micro is available in the AWS free tier
        InstanceType = "t2.micro",
        VpcSecurityGroupIds = { group.Id }, // reference the security group resource above
        Ami = ami.Apply(x => x.Id),
        UserData = userData,             // <-- ADD THIS LINE
    });
    

    Note that the userData script is defined inline in a string. In this example, index.html will be created in the root directory /. Because you are using a programming language to write your Pulumi program, you could also read this from a file, construct this string programmatically, or even build up a string that depends on other resources defined in your program. You’ll see in later sections how to deploy and version the application code of your program in a variety of different ways using Pulumi.

    Run pulumi up to preview and deploy the changes. You’ll see two changes: the ingress property of the SecurityGroup will be updated in-place. Secondly, the Instance will be replaced with a new EC2 instance which will run the new script on startup. Pulumi understands which changes to a given cloud resource can be made in place, which require replacement, and computes the minimally disruptive change to achieve the desired state.

    Previewing update (webserver-dev):
    
         Type                      Name                     Plan        Info
         pulumi:pulumi:Stack       myproject-webserver-dev
     ~   ├─ aws:ec2:SecurityGroup  webserver-secgrp         update      [diff: ~ingress]
     +-  └─ aws:ec2:Instance       webserver-www            replace     [diff: +userData~securityGroups]
    
    Resources:
        ~ 1 to update
        +-1 to replace
        2 changes. 1 unchanged
    

    When prompted to confirm your update, you may review the planned changes to your stack resources by selecting details.

    Do you want to perform this update? details
      pulumi:pulumi:Stack: (same)
        [urn=urn:pulumi:webserver-dev::myproject::pulumi:pulumi:Stack::myproject-webserver-dev]
        ~ aws:ec2/securityGroup:SecurityGroup: (update)
            [id=sg-0317c16c7015d7fd0]
            [urn=urn:pulumi:webserver-dev::myproject::aws:ec2/securityGroup:SecurityGroup::webserver-secgrp]
            [provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
          ~ ingress: [
              ~ [0]: {
                      ~ cidrBlocks : [
                          ~ [0]: "0.0.0.0/0" => "0.0.0.0/0"
                        ]
                      - description: ""
                      ~ fromPort   : 22 => 22
                      ~ protocol   : "tcp" => "tcp"
                      ~ self       : false => false
                      ~ toPort     : 22 => 22
                    }
              + [1]: {
                      + cidrBlocks: [
                      +     [0]: "0.0.0.0/0"
                        ]
                      + fromPort  : 80
                      + protocol  : "tcp"
                      + self      : false
                      + toPort    : 80
                    }
            ]
        ++aws:ec2/instance:Instance: (create-replacement)
            [id=i-0a639b62c37bf712c]
            [urn=urn:pulumi:webserver-dev::myproject::aws:ec2/instance:Instance::webserver-www]
            [provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
          ~ securityGroups: [
              ~ [0]: "webserver-secgrp-2398ba7" => output<string>
            ]
          + userData      : "#!/bin/bash\necho \"Hello, World!\" > index.html\nnohup python -m SimpleHTTPServer 80 &"
        +-aws:ec2/instance:Instance: (replace)
            [id=i-0a639b62c37bf712c]
            [urn=urn:pulumi:webserver-dev::myproject::aws:ec2/instance:Instance::webserver-www]
            [provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
          ~ securityGroups: [
              ~ [0]: "webserver-secgrp-2398ba7" => output<string>
            ]
          + userData      : "#!/bin/bash\necho \"Hello, World!\" > index.html\nnohup python -m SimpleHTTPServer 80 &"
        --aws:ec2/instance:Instance: (delete-replaced)
            [id=i-0a639b62c37bf712c]
            [urn=urn:pulumi:webserver-dev::myproject::aws:ec2/instance:Instance::webserver-www]
            [provider=urn:pulumi:webserver-dev::myproject::pulumi:providers:aws::default_1_2_1::eec9bbfb-0881-4f75-a0cb-35395a0240e2]
    

    Select yes to confirm the update.

    You can use pulumi stack output to get the value of stack outputs from the CLI. To do so, curl the EC2 instance to confirm that the HTTP server is running. Stack outputs can also be viewed in the Pulumi Cloud.

    $ curl $(pulumi stack output publicHostName)
    Hello, World!
    

    Clean Up

    Before moving on, tear down the resources that are part of your stack to avoid incurring any charges.

    1. Run pulumi destroy to tear down all resources. You'll be prompted to make sure you really want to delete these resources. A destroy operation may take some time, since Pulumi waits for the resources to finish shutting down before it considers the destroy operation to be complete.
    2. To delete the stack itself, run pulumi stack rm. Note that this command deletes all deployment history from the Pulumi Service.

    Summary

    In this tutorial, we showed you how to use Pulumi programs to create and manage cloud resources in AWS, using TypeScript, JavaScript, Python, or C#.

    You also learned how to work with the Pulumi CLI. To recap:

    • Run pulumi new <cloud>-<language> --name myproject to create a new project from a language and cloud template.
    • Run pulumi up to preview and update your infrastructure.
    • Run pulumi destroy to clean up your resources.
    • Run pulumi stack rm to delete your stack.

    For a similar example in other languages and clouds, see the Pulumi examples repo.

    Next Steps

    aws logo

    Try AWS Native preview for resources not in the classic version.

    AWS Classic v6.42.0 published on Wednesday, Jun 26, 2024 by Pulumi