Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Deploy to AWS!

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

So far, we've been deploying to a virtual machine. But... there's nothing stopping us from deploying to... the CLOUD! Let's try it - let's deploy to Amazon EC2. This is not an exhaustive tutorial about using EC2... but let's at least get our feet wet and see if we can get into some trouble!


Want to properly deploy with Ansible? Check out Ansistrano.

Manually Launching an EC2 Instance

I'm already on my EC2 dashboard. In a few minutes, we're going to use Ansible to actually launch a new instance. But for now, just hit "Launch Instance" to do it by hand. I'm looking for an image that's similar to what we're using with Vagrant: Ubuntu 14.04. Select that image, use the micro instance size, and just use the default settings on the next screens.


The instance id we used is ami-41d48e24 if you need to find it manually.

For the security group, I'm going to select a group I already created: "Web Access Testing." The important thing is to allow port 22 for SSH and ports 80 and 443 for web stuff. Hit "Review and Launch", then "Launch" that instance!

Bah! What a tease! No instance yet: we need to choose a key pair for SSH. I already created a pair for this tutorial called Ansible_AWS_tmp. When we launch the instance, instead of logging in with a username and password, we will SSH with a username and a private key. You'll need to create your own key pair. When you do that, you'll download its private key. In this case, the file is called Ansible_AWS_tmp.pem and I already downloaded it.

Ok, now launch the instance! Cool! Click to view its progress.

Configuring the new Host

While it's loading, let's get to work!

This new server represents a new host. In hosts.ini we have a local group with one server and a vb group with one server. Create a new group called aws:

... lines 1 - 6
... lines 8 - 13

Below, we need the IP to the server. Wait for it to boot.

When it's ready, copy its public IP address, go back to the hosts file, and paste! This time, set ansible_user to ubuntu: that's the user that's setup for this image. And instead of a password, use ansible_ssh_private_key_file= and put the path to your downloaded private key: ~/.ssh/Ansible_AWS_tmp.pem for me:

... lines 1 - 6
[aws] ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/KnpU-Tutorial.pem
... lines 9 - 13


Depends on your AWS instance, you may need to specify a new path to Python interpreter. By default, Ansible uses /usr/bin/python but new AWS instances have Python 3 pre-installed and the path to it is /usr/bin/python3. You can specify the correct Python interpreter path explicitly with ansible_python_interpreter key in case you got an error from Ansible about not found Python:

# ansible/hosts.ini

# ...

[aws] ansible_python_interpreter=/usr/bin/python3 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/KnpU-Tutorial.pem

Host Group Children

Here's where things get cool! I want to run our playbook against the virtual machine and my EC2 instance. Because... it's totally valid to build two servers at once! That's where Ansible shines!

Right now, each lives under its own host group - vb and aws:

... lines 1 - 3
[vb] ansible_user=vagrant ansible_ssh_pass=vagrant
[aws] ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/KnpU-Tutorial.pem
... lines 9 - 13

Inside of our playbook, we've configured the play to only run against the vb group:

- hosts: vb
... lines 3 - 171

How could we run that against the hosts in the vb group and in the aws group?

With a host group... group! Check it out: create a new group called webserver, but add a :children after. That special children syntax allows us to list other host groups below this: vb and aws:

... lines 1 - 3
[vb] ansible_user=vagrant ansible_ssh_pass=vagrant
[aws] ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/KnpU-Tutorial.pem

Yep, we now have a new group - webserver - that's a combination of these two.

Back in the playbook, change vb to webserver:

- hosts: webserver
... lines 3 - 171

Running the Playbook

Deep breath. Run the playbook:

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini --ask-vault-pass

Enter beefpass and deploy to the prod environment. You'll need to verify the authenticity of the new host this first time. And, by the way, you can disable this check in Ansible.

Now, watch the magic! You'll start to see it execute each task against both servers. The first time we do this, it'll take awhile: the new EC2 server is being setup from scratch. And, I was cheap - it's only a micro instance.

While we're waiting, let's go copy the IP address to the new server again. Temporarily open a new terminal tab and edit the /etc/hosts file:

sudo vim /etc/hosts

To test thing, update mootube.l to point to the IP address of the EC2 instance:

# /etc/hosts
# ...
# mootube.l mootube.l

Then, save, quit and close the tab.

Even though Ansible is still working, if I go to http://mootube.l right now, I see the "Welcome to Nginx" page. Ha, cool! Ansible is already part way through the process!

Let's try to be patient... but also fast forward!

Done! And beautiful - it finished with no errors. That's kind of amazing: we launched a new cloud server from scratch... with no changes. Refresh the page. Got it! Welcome to MooTube, hosted on our fancy new EC2 instance. Notice that there's no data because we loaded in the prod environment: so the fixtures didn't run.

The only weird thing is that after changing my hosts file, I can't access MooTube on my VM anymore. But, we can solve that with host group vars.

Leave a comment!

This tutorial is built using an older version of Symfony, but the core concepts of Ansible are still valid. New versions of Ansible may contain some features that we don't use here.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.12
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.0
        "snc/redis-bundle": "^2.0", // 2.0.0
        "predis/predis": "^1.1", // v1.1.1
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.8
        "symfony/phpunit-bridge": "^3.0", // v3.1.4
        "doctrine/data-fixtures": "^1.1", // 1.3.3
        "hautelook/alice-bundle": "^1.3" // v1.4.1