├── .gitignore ├── public ├── js │ └── set-background.js ├── img │ └── tweet.svg ├── css │ ├── gradients.css │ └── styles.css └── index.html ├── Dockerfile ├── app.js ├── task-execution-assume-role.json ├── package.json ├── node-task-definition.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /public/js/set-background.js: -------------------------------------------------------------------------------- 1 | var idx = Math.floor(new Date().getHours()); 2 | var body = document.getElementsByTagName("body")[0]; 3 | body.className = "heaven-" + idx; 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.4-alpine 2 | 3 | RUN mkdir /app 4 | WORKDIR /app 5 | 6 | COPY package.json package.json 7 | RUN npm install && mv node_modules /node_modules 8 | 9 | COPY . . 10 | 11 | LABEL maintainer="Austin Loveless" 12 | 13 | CMD node app.js 14 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const port = 80; 3 | const app = express(); 4 | const path = require("path"); 5 | const publicDir = path.join(__dirname, "public"); 6 | 7 | app.use(express.static(publicDir)); 8 | 9 | app.listen(port, () => { 10 | console.log(`Listening on port ${port}`); 11 | }); 12 | -------------------------------------------------------------------------------- /task-execution-assume-role.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": "ecs-tasks.amazonaws.com" 9 | }, 10 | "Action": "sts:AssumeRole" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Docker-on-AWS", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/austinloveless/Docker-on-AWS.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/austinloveless/Docker-on-AWS/issues" 18 | }, 19 | "homepage": "https://github.com/austinloveless/Docker-on-AWS#readme", 20 | "dependencies": { 21 | "express": "^4.17.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /node-task-definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "nodejs-fargate-task", 3 | "networkMode": "awsvpc", 4 | "executionRoleArn": "arn:aws:iam::xxxxx:role/ecsTaskExecutionRole", 5 | "containerDefinitions": [ 6 | { 7 | "name": "nodejs-app", 8 | "image": "xxxxx.dkr.ecr.us-east-1.amazonaws.com/docker-on-aws/nodejs:latest", 9 | "portMappings": [ 10 | { 11 | "containerPort": 80, 12 | "hostPort": 80, 13 | "protocol": "tcp" 14 | } 15 | ], 16 | "essential": true 17 | } 18 | ], 19 | "requiresCompatibilities": [ 20 | "FARGATE" 21 | ], 22 | "cpu": "256", 23 | "memory": "512" 24 | } -------------------------------------------------------------------------------- /public/img/tweet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 15 | 16 | -------------------------------------------------------------------------------- /public/css/gradients.css: -------------------------------------------------------------------------------- 1 | /* Gradients */ 2 | .heaven-0 { background: #00000c; } 3 | .heaven-1 { background: linear-gradient(to bottom, #020111 85%, #191621 100%); } 4 | .heaven-2 { background: linear-gradient(to bottom, #020111 60%, #20202c 100%); } 5 | .heaven-3 { background: linear-gradient(to bottom, #020111 10%, #3a3a52 100%); } 6 | .heaven-4 { background: linear-gradient(to bottom, #20202c 0%, #515175 100%); } 7 | .heaven-5 { background: linear-gradient(to bottom, #40405c 0%, #6f71aa 80%, #8a76ab 100%); } 8 | .heaven-6 { background: linear-gradient(to bottom, #4a4969 0%, #7072ab 50%, #cd82a0 100%); } 9 | .heaven-7 { background: linear-gradient(to bottom, #757abf 0%, #8583be 60%, #eab0d1 100%); } 10 | .heaven-8 { background: linear-gradient(to bottom, #82addb 0%, #ebb2b1 100%); } 11 | .heaven-9 { background: linear-gradient(to bottom, #94c5f8 1%, #a6e6ff 70%, #b1b5ea 100%); } 12 | .heaven-10 { background: linear-gradient(to bottom, #b7eaff 0%, #94dfff 100%); } 13 | .heaven-11 { background: linear-gradient(to bottom, #9be2fe 0%, #67d1fb 100%); } 14 | .heaven-12 { background: linear-gradient(to bottom, #90dffe 0%, #38a3d1 100%); } 15 | .heaven-13 { background: linear-gradient(to bottom, #57c1eb 0%, #246fa8 100%); } 16 | .heaven-14 { background: linear-gradient(to bottom, #2d91c2 0%, #1e528e 100%); } 17 | .heaven-15 { background: linear-gradient(to bottom, #2473ab 0%, #1e528e 70%, #5b7983 100%); } 18 | .heaven-16 { background: linear-gradient(to bottom, #1e528e 0%, #265889 50%, #9da671 100%); } 19 | .heaven-17 { background: linear-gradient(to bottom, #1e528e 0%, #728a7c 50%, #e9ce5d 100%); } 20 | .heaven-18 { background: linear-gradient(to bottom, #154277 0%, #576e71 30%, #e1c45e 70%, #b26339 100%); } 21 | .heaven-19 { background: linear-gradient(to bottom, #163c52 0%, #4f4f47 30%, #c5752d 60%, #b7490f 80%, #2f1107 100%); } 22 | .heaven-20 { background: linear-gradient(to bottom, #071b26 0%, #071b26 30%, #8a3b12 80%, #240e03 100%); } 23 | .heaven-21 { background: linear-gradient(to bottom, #010a10 30%, #59230b 80%, #2f1107 100%); } 24 | .heaven-22 { background: linear-gradient(to bottom, #090401 50%, #4B1d06 100%); } 25 | .heaven-23 { background: linear-gradient(to bottom, #00000c 80%, #150800 100%); } 26 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | } 4 | 5 | html, body { 6 | height: 100%; 7 | } 8 | 9 | html { 10 | margin: 0; 11 | padding: 0; 12 | font-family: "Helvetica Neue", Roboto, Arial, sans-serif; 13 | font-size: 62.5%; 14 | color: white; 15 | background: linear-gradient(to bottom, #2473ab 0%,#1e528e 70%,#5b7983 100%); 16 | } 17 | 18 | body { 19 | font-size: 1.8rem; 20 | } 21 | 22 | h1, h2, h3 { 23 | font-weight: normal; 24 | } 25 | 26 | h1 { 27 | font-size: 3.5rem; 28 | margin-bottom: 0.5rem; 29 | } 30 | 31 | a { 32 | color: white; 33 | text-decoration: none; 34 | } 35 | 36 | .wrapper { 37 | min-height: 100%; 38 | height: auto !important; 39 | height: 100%; 40 | margin: 0 auto -7rem; 41 | position: relative; 42 | } 43 | 44 | footer, .push { 45 | height: 7rem; 46 | } 47 | 48 | footer .footer-contents { 49 | padding: 0 5rem; 50 | position: relative; 51 | } 52 | 53 | .website-nav { 54 | position: relative; 55 | padding: 5rem; 56 | } 57 | 58 | .website-nav ul { 59 | list-style: none; 60 | margin: 0; 61 | padding: 0; 62 | } 63 | 64 | .website-nav ul li { 65 | float: left; 66 | padding: 0.5rem 2rem 0.5rem 0; 67 | line-height: 21px; 68 | } 69 | 70 | .website-nav ul li a { 71 | color: white; 72 | text-decoration: none; 73 | } 74 | 75 | .home-link { 76 | font-weight: bold; 77 | } 78 | 79 | .message { 80 | position: relative; 81 | padding: 0 5rem; 82 | margin-bottom: 3rem; 83 | width: auto; 84 | } 85 | 86 | .message .twitter-link { 87 | float: left; 88 | margin-right: 20px; 89 | } 90 | 91 | .message .twitter-link img { 92 | width: 40px; 93 | height: 40px; 94 | } 95 | 96 | .message .text { 97 | float: left; 98 | } 99 | 100 | .message:after, .website-nav:after { 101 | content: " "; 102 | display: table; 103 | clear: both; 104 | } 105 | 106 | .graphics { 107 | display: none; 108 | position: absolute; 109 | bottom: 0; 110 | left: 0; 111 | margin-bottom: 0; 112 | } 113 | 114 | .graphics .tower svg { 115 | width: calc(100vw); 116 | height: calc(90vh); 117 | } 118 | 119 | .graphics .cloud { 120 | top: 30rem; 121 | position: relative; 122 | } 123 | 124 | .page-content { 125 | padding: 1rem 5rem; 126 | } 127 | 128 | .page-content p { 129 | margin-bottom: 1rem; 130 | } 131 | 132 | .path { 133 | stroke-dasharray: 4000; 134 | stroke-dashoffset: 4000; 135 | animation: dash 5s linear forwards; 136 | } 137 | 138 | @keyframes dash { 139 | to { 140 | stroke-dashoffset: 0; 141 | } 142 | } 143 | 144 | @media (min-height: 500px) and (min-width: 700px) { 145 | .message { 146 | padding: 0; 147 | width: 50%; 148 | left: calc(50vw - 5rem); 149 | top: calc(15vh - 5rem); 150 | } 151 | 152 | .message .text { 153 | padding-right: 0rem; 154 | } 155 | 156 | .graphics { 157 | display: block; 158 | margin-bottom: 6rem; 159 | } 160 | 161 | .message .twitter-link img { 162 | width: 70%; 163 | height: 70%; 164 | } 165 | 166 | .page-content { 167 | padding: 3rem 5rem; 168 | } 169 | } 170 | 171 | @media (min-height: 500px) and (min-width: 1240px) { 172 | .message { 173 | left: calc(50vw - 10rem); 174 | top: calc(25vh - 5rem); 175 | } 176 | 177 | h1 { 178 | font-size: 5rem; 179 | } 180 | 181 | .message .twitter-link img { 182 | width: 80%; 183 | height: 80%; 184 | } 185 | } 186 | 187 | @media (min-aspect-ratio: 11/5) { 188 | .graphics { 189 | display: none; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Intro To Docker 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 18 | 26 | 30 | 34 | 38 | 42 | 43 |
44 |
45 |
46 | 47 |
48 |

Intro to Docker!

49 |

You just created a Node Express web application with Docker!

50 |
51 |
52 |
53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Fargate: Running a serverless Node.js app on AWS ECS. 2 | 3 | ## Project Summary 4 | 5 | We're going to containerize a node.js project that renders a simple static site and deploy it to an Amazon ECS Fargate cluster. I will supply all the code at https://github.com/austinloveless/Docker-on-AWS. 6 | 7 | The Tutorial assumes you have a basic understanding of Docker. If you are brand new to to Docker you can read my previous [post](https://medium.com/@awsmeetupgroup/docker-on-aws-1855b825de5e) introducing you to Docker. 8 | 9 | ## Installing Prerequisites 10 | 11 | ### Downloading Docker Desktop. 12 | 13 | If you are on a Mac go to https://docs.docker.com/docker-for-mac/install/ or Windows go to https://docs.docker.com/docker-for-windows/install/. Follow the installation instructions and account setup. 14 | 15 | ### Installing node.js 16 | 17 | Download node.js [here](https://nodejs.org/en/). 18 | 19 | ### Installing the AWS CLI 20 | 21 | Follow the instructions [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html). 22 | 23 | ## Project setup 24 | 25 | Now that we have our prerequisites installed we can build our application. This project isn't going to focus on the application code, the point is to get more familiar with Docker and AWS. So you can download the [Repo](https://github.com/austinloveless/Docker-on-AWS) and change directories into the `Docker-on-AWS` directory. 26 | 27 | If you wanted to run the app locally and say screw docker you can run `npm install` in side the `Docker-on-AWS` directory. Then run `node app.js`. To see the site running locally visit `http://localhost:80`. 28 | 29 | Now that we have docker installed and the repo downloaded we can look at the `Dockerfile`. You can think of it as a list of instructions for docker to execute when building a container or the blueprints for the application. 30 | 31 | ``` 32 | FROM node:12.4-alpine 33 | 34 | RUN mkdir /app 35 | WORKDIR /app 36 | 37 | COPY package.json package.json 38 | RUN npm install && mv node_modules /node_modules 39 | 40 | COPY . . 41 | 42 | LABEL maintainer="Austin Loveless" 43 | 44 | CMD node app.js 45 | 46 | ``` 47 | 48 | At the top we are declaring our runtime which is `node:12.4-alpine`. This is basically our starting point for the application. We're grabbing this base image "FROM" the official docker hub [node image](https://hub.docker.com/_/node). 49 | 50 | If you go to the link you can see 12.4-alpine. The "-alpine" is a much smaller base image and is recommended by docker hub "when final image size being as small as possible is desired". Our application is very small so we're going to use an alpine image. 51 | 52 | Next in the Dockerfile we're creating an `/app` directory and setting our working directory within the docker container to run in `/app`. 53 | 54 | After that we're going to "COPY" the `package.json` file to `package.json` on the docker container. We then install our dependencies from our `node_modules`. "COPY" the entire directory and run the command `node app.js` to start the node app within the docker container. 55 | 56 | ## Using Docker 57 | 58 | Now that we've gone over the boring details of a Dockerfile, lets actually build the thing. 59 | 60 | So when you installed Docker Desktop it comes with a few tools. Docker Command Line, Docker Compose and Docker Notary command line. 61 | 62 | We're going to use the Docker CLI to: 63 | 64 | - Build a docker image 65 | 66 | - Run the container locally 67 | 68 | ### Building an image 69 | 70 | The command for building an image is `docker build [OPTIONS] PATH | URL | -`. You can go to the [docs](https://docs.docker.com/engine/reference/commandline/build/) to see all the options. 71 | 72 | In the root directory of the application you can run `docker build -t docker-on-aws .`. This will tag our image as "docker-on-aws". 73 | 74 | To verify you successfully created the image you can run `docker images`. Mine looks like `docker-on-aws latest aa68c5e51a8e About a minute ago 82.8MB`. 75 | 76 | ### Running a container locally 77 | 78 | Now we are going to run our newly created image and see docker in action. Run `docker run -p 80:80 docker-on-aws`. The `-p` is defining what port you want your application running on. 79 | 80 | ![dockerrun](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/Screen+Shot+2020-01-13+at+6.06.33+PM.png) 81 | 82 | You can now visit http://localhost:80. 83 | 84 | To see if your container is running via the CLI you can open up another terminal window and run `docker container ls`. To stop the image you can run `docker container stop `. Verify it stopped with `docker container ls` again or `docker ps`. 85 | 86 | ## Docker on Amazon ECS 87 | 88 | We're going to push the image we just created to Amazon ECR, Elastic Container Registry, create an ECS cluster and download the image from ECR onto the ECS cluster. 89 | 90 | Before we can do any of that we need to create an IAM user and setup our AWS CLI. 91 | 92 | 93 | ### Configuring the AWS CLI 94 | 95 | We're going to build everything with the AWS CLI. 96 | 97 | Go to the AWS Console and search for IAM. Then go to "Users" and click the blue button "Add User". 98 | 99 | ![Add User](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/IAMAddUser.png) 100 | 101 | Create a user name like "ECS-User" and select "Programmatic Access". 102 | 103 | ![Programmatic Access](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/ProgrammaticAccess.png) 104 | 105 | Click "Next: Permissions" and select "Attach exisiting policies directly" at the top right. Then you should see "AdministratorAccess", we're keeping this simple and giving admin access. 106 | 107 | Click "Next: Tags" and then "Next: Review", we're not going to add any tags, and "Create user". 108 | 109 | Now you should see a success page and an "Access key ID" and a "Secret access key". 110 | 111 | ![Access Key](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/AccessKey.png) 112 | 113 | Take note of both the Access Key ID and Secret Access key. We're going to need that to configure the AWS CLI. 114 | 115 | Open up a new terminal window and type `aws configure` and input the keys when prompted. Set your region as `us-east-1`. 116 | 117 | ![AWS Configure](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/AWSConfigure.png) 118 | 119 | ### Creating an ECS Cluster 120 | 121 | To create an ECS Cluster you can run the command `aws ecs create-cluster --cluster-name docker-on-aws`. 122 | 123 | We can validate that our cluster is created by running `aws ecs list-clusters`. 124 | 125 | ![Create Cluster](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/ECSCreate.png). 126 | 127 | If you wanted to delete the cluster you can run `aws ecs delete-cluster --cluster docker-on-aws` 128 | 129 | 130 | ### Pushing an Image to Amazon ECR 131 | 132 | Now that the CLI is configured we can tag our docker image and upload it to ECR. 133 | 134 | First, we need to login to ECR. 135 | 136 | Run the command `aws ecr get-login --no-include-email`. The output should be `docker login -u AWS -p` followed by a token that is valid for 12 hours. Copy and run that command as well. This will authenticate you with Amazon ECR. If successful you should see "Login Succeeded". 137 | 138 | Create an ECR Repository by running `aws ecr create-repository --repository-name docker-on-aws/nodejs`. That's the cluster name followed by the image name. Take note of the `repositoryUri` in the output. 139 | 140 | ![Create repository](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/CreateRepo.png) 141 | 142 | We have to tag our image so we can push it up to ECR. 143 | 144 | Run the command `docker tag docker-on-aws .dkr.ecr.us-east-1.amazonaws.com/docker-on-aws/nodejs`. Verify you tagged it correctly with `docker images`. 145 | 146 | Now push the image to your ECR repo. Run `docker push .dkr.ecr.us-east-1.amazonaws.com/docker-on-aws/nodejs`. Verify you pushed the image with `aws ecr list-images --repository-name docker-on-aws/nodejs`. 147 | 148 | ![Verify Image](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/ecrList.png) 149 | 150 | 151 | ## Uploading a node.js app to ECS 152 | 153 | The last few steps involve pushing our node.js app to the ECS cluster. To do that we need to create and run a task definition and a service. 154 | Before we can do that we need to create an IAM role to allow us access to ECS. 155 | 156 | ### Creating an ecsTaskExecutionRole with the AWS CLI 157 | 158 | I have created a file called `task-execution-assume-role.json` that we will use to create the ecsTaskExecutionRole from the CLI. 159 | 160 | ```{ 161 | "Version": "2012-10-17", 162 | "Statement": [ 163 | { 164 | "Sid": "", 165 | "Effect": "Allow", 166 | "Principal": { 167 | "Service": "ecs-tasks.amazonaws.com" 168 | }, 169 | "Action": "sts:AssumeRole" 170 | } 171 | ] 172 | } 173 | ``` 174 | 175 | You can run `aws iam create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://task-execution-assume-role.json ` to create the role. Take note of the `"Arn"` in the output. 176 | 177 | ![Iam](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/IamCreateRoleCLI.png) 178 | 179 | Then run `aws iam attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy` to attach the "AmazonECSTaskExecutionRolePolicy". 180 | 181 | Take the `"Arn"` you copied earlier and paste it into the `node-task-definition.json` file for the `executionRoleArn`. 182 | 183 | ``` 184 | { 185 | "family": "nodejs-fargate-task", 186 | "networkMode": "awsvpc", 187 | "executionRoleArn": "arn:aws:iam::xxxxx:role/ecsTaskExecutionRole", 188 | "containerDefinitions": [ 189 | { 190 | "name": "nodejs-app", 191 | "image": "xxxxx.dkr.ecr.us-east-1.amazonaws.com/docker-on-aws/nodejs:latest", 192 | "portMappings": [ 193 | { 194 | "containerPort": 80, 195 | "hostPort": 80, 196 | "protocol": "tcp" 197 | } 198 | ], 199 | "essential": true 200 | } 201 | ], 202 | "requiresCompatibilities": [ 203 | "FARGATE" 204 | ], 205 | "cpu": "256", 206 | "memory": "512" 207 | } 208 | ``` 209 | 210 | ### Registering an ECS Task Definition 211 | 212 | Once your IAM role is created and you updated the `node-task-definition.json` file with your `repositoryUri` and `executionRoleArn` and you can register your task. 213 | 214 | Run `aws ecs register-task-definition --cli-input-json file://node-task-definition.json ` 215 | 216 | ### Creating and ECS Service 217 | 218 | The final step to this process is creating a service that will run our task on the ECS Cluster. 219 | 220 | We need to create a security group with port 80 open and we need a list of public subnets for our network configuration. 221 | 222 | To create the security group run `aws ec2 create-security-group --group-name ecs-security-group --description "Security Group us-east-1 for ECS"`. That will output a security group ID. Take note of this ID. You can see information about the security group by running 223 | `aws ec2 describe-security-groups --group-id `. 224 | 225 | It will show that we don't have any IpPermissions so we need to add one to allow port 80 for our node application. Run `aws ec2 authorize-security-group-ingress --group-id --protocol tcp --port 80 --cidr 0.0.0.0/0` to add port 80. 226 | 227 | Now we need to get a list of our public subnets and then we can create the ECS Service. 228 | 229 | Run `aws ec2 describe-subnets` in the output you should see `"SubnetArn"` for all the subnets. At the end of that line you see "subnet-XXXXXX" take note of those subnets. Note: if you are in `us-east-1` you should have 6 subnets 230 | 231 | Finally we can create our service. 232 | 233 | Replace the subnets and security group Id with yours and run ` aws ecs create-service --cluster docker-on-aws --service-name nodejs-service --task-definition nodejs-fargate-task:1 --desired-count 1 --network-configuration "awsvpcConfiguration={subnets=[ subnet-XXXXXXXXXX, 234 | subnet-XXXXXXXXXX, 235 | subnet-XXXXXXXXXX, 236 | subnet-XXXXXXXXXX, 237 | subnet-XXXXXXXXXX, 238 | subnet-XXXXXXXXXX],securityGroups=[sg-XXXXXXXXXX],assignPublicIp=ENABLED}" --launch-type "FARGATE"`. 239 | 240 | Running this will create the service `nodejs-service` and run the task `nodejs-fargate-task:1`. The `:1` is the revision count. When you update the task definition the revision count will go up. 241 | 242 | ## Viewing your nodejs application. 243 | 244 | Now that you have everything configured and running it's time to view the application in the browser. 245 | 246 | To view the application we need to get the public IP address. Go to the ECS dashboard, in the AWS Console, and click on your cluster. 247 | 248 | ![ClusterConsole](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/Screen+Shot+2020-01-13+at+5.52.10+PM.png) 249 | 250 | Then click the "tasks" tab and click your task ID. 251 | 252 | ![Tasks](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/Tasks.png) 253 | 254 | From there you should see a network section and the "Public IP". 255 | 256 | ![NetworkConfig](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/Screen+Shot+2020-01-13+at+5.54.07+PM.png) 257 | 258 | 259 | Paste the IP address in the browser and you can see the node application. 260 | 261 | ![nodeApp](https://awsmeetupgroupreadmeimages.s3.amazonaws.com/Docker-on-AWS/NodeApp.png) 262 | 263 | Bam! We have a simple node application running in an Amazon ECS cluster powered by Fargate. 264 | 265 | If you don't want to use AWS and just want to learn how to use Docker check out my last [blog](https://medium.com/@awsmeetupgroup/tutorial-docker-and-node-js-2d7fde6eb38b) 266 | 267 | Also, I attached some links here for more examples of task definitions you could use for other applications. 268 | 269 | https://docs.aws.amazon.com/AmazonECS/latest/developerguide/example_task_definitions.html 270 | 271 | https://github.com/aws-samples/aws-containers-task-definitions/blob/master/ 272 | --------------------------------------------------------------------------------