End-to-End Website Build

Mac, VSCode, Django, Python, HTML, CSS, Bootstrap, PostgreSQL, AWS

5 June 2023

This post documents an end-to-end process for website development and deployment. There are thousands of tools, methods, and processes, and this is just one option. I am still a new developer, learning as a I write this post, so this is a beginner/intermediate tutorial in web development and deployment, however some familiarity with the programming languages and the command line are helpful. The processes followed in this tutorial leverage and combine the steps provided in the Django Tutorial and the AWS Tutorial. In this post I let you know at which points it makes sense to complete these tutorials, as I believe they will help your understanding. As mentioned, this post has a lot of overlap with the 2 previously mentioned tutorials (along with other steps to tie it all together), however I won't give much context to why we do things in Django or AWS. I intend this post to be helpful for other new developers, and for myself as a reminder in future projects. In past efforts I've struggled to make progress in my web development projects, not because I had a challenge learning any of these languages, but because I struggled with 2 things:

  • Learning and establishing the interfaces between the tools, and
  • Establishing a decently efficient continuous integration and continuous deployment (CI/CD) process

Thus, we focus on setting up your tools/interfaces and establishing a CI/CD process. I am not a professional, and would love to hear feedback from the experts out there.

The steps in this post are based on the steps I've taken to develop this website. I did not use any AI or wordpress-like tools to develop the site (ironic that my first website dev/deploy is not long after the release of several AI tools that could have created this site for me). I wrote all the code and deployed it with the help of the various tools and frameworks below:

  • 15" Macbook Pro 2019
  • Visual Studio Code
  • Atom text editor for miscellaneous handling of text files
  • Django framework using python, javascript, HTML and CSS
  • PostgreSQL database
  • Bootstrap for CSS styling
  • AWS hosting with Elastic Beanstalk and Route 53 domain name service
    • Note: AWS Elastic Beanstalk employs the use of several other AWS resources. In this post we'll use the EC2, RDS, and S3 services

Overview

There are 3 major sections:

  1. Initial Setup
  2. Django Project
  3. AWS Deployment

Initial Setup

Download Tools to Get Started

If you don't already have visual studio code, python3, git, and PostgreSQL you can install them at the following links. Check if python3, git, and postgres are already installed by opening a command line and typing python3 --version, git --version and postgres --version. See note below regarding pre-installed python3 on mac.

  • Download visual studio code here

  • Download python3 here

  • Download git here

  • Download PostgreSQL here

Python3 Note:

When you type python3 in the terminal you may get a pop-up to install xcode developer tools. There are 2 options to handle:

  1. Install xcode developer tools
    1. Accept and install the xcode developer tools
    2. After the install, the python3 command should work in the terminal, but it will likely be using the pre-installed mac python distribution, not the one you just downloaded. You can check by typing which python3 and you might get an output like: /usr/local/bin/python3.
    3. To switch to your new python installation go to the application folder where you just installed python and locate the "Update Shell Profile.command" and double click
    4. This will associate your new python installation with the python3 command line command. You should check that you are using the new installation by typing: which python3 and you should get an output that looks like: /Library/Frameworks/Python.framework/Versions/3.11/bin/python3
  2. Don't install xcode developer tools
    1. If you prefer not to install xcode developer tools, go straight to step 3 in the above. I have not confirmed this will work, but it should, and I will check in the future and update this post accordingly.

Git Note:

If you installed the xcode developer tools you should have git as well

Tool Setup and Project Directory Prep

Now let's set up our tools and working directories

  1. Create a project directory to locate code, references, docs, workspace, etc. The figure below is what I usally start with. Note that there is not a folder for our source code yet, we will add that later in the django section.
  2. Open VSCode, go to file>open, and select the project directory you just created
  3. Now setup up your VSCode windows. In the upper right corner you can customize your layout. I prefer to have have the Primary Side Bar and Panel activated, while the Secondary Side Bar is not activated.
  4. In the panel there are three tabs: PROBLEMS, OUTPUT, DEBUG CONSOLE, and TERMINAL. Most often I have the TERMINAL open. You can check that VSCode is using the correct python installation with: which pyhon3
  5. Now install VSCode extensions via the extensions tab on the left side of your VSCode window. I use the following extensions for django projects: Python, Pylance, Django, and HTML Boilerplate

You are ready to start working on your Django Project!

Django Project

Create Virtual Environment

We start by creating our virtual environment through the command line

  1. In the terminal navigate to your project directory, you can check your present working directory with: pwd
  2. Now create the virtual environment: python3 -m venv <name of your virtual env, e.g. testenv>
  3. You should see a new directory containing your virtual environment packages. Note, in this post I created an environment named "testenv", but you should replace "testenv" with the name of the environment you created
  4. Activate the virtual environment with source <name of your virtual env, e.g. testenv>/bin/activate. You should see the name of your virtual environment in parentheses at the start for your command line.
  5. Now check the packages installed in the venv with: pip freeze. Nothing will be returned in the terminal because we have not installed any packages yet. Going forward we will almost always have have our virtual environment activated, so remember to check your terminal to see what environment you are working in.
  6. Now download the packages required for the rest of project development and deployment.
    1. Create a file in your project directory titled "requirements.txt"
    2. Place the following text in the "requirements.txt" file and save
    3. asgiref==3.6.0
      boto3==1.26.142
      botocore==1.29.142
      Django==4.2.1
      django-storages==1.13.2
      jmespath==1.0.1
      psycopg==3.1.9
      psycopg-binary==3.1.9
      python-dateutil==2.8.2
      python-decouple==3.8
      s3transfer==0.6.1
      six==1.16.0
      sqlparse==0.4.4
      typing_extensions==4.5.0
      urllib3==1.26.16
    4. Now we install the packages
      1. First we typically want to make sure we have the latest pip veresion installed: pip install --upgrade pip
      2. Now make sure your virtual environment is activated, and install the packages with: pip install -r requirements.xt

Start the Django Project

Now we start our django project. If you have not completed the Dango Tutorial this would be a good time to complete it.

  1. Make sure venv is activated and type: django-admin startproject <name of your django project>
  2. The above will create a django project folder. We will re-organize the directories slightly because the django project folder that was just created is where we will be doing all of our development (not the top level directory). Relocate the virtual environment folder that you created in a previous step into the django project folder that was just created. Your directory should now look something like below:
  3. In VSCode now change your VSCode root directory on the left side panel to your django project directory that you just created.
  4. Now save our VSCode workspace. In VSCode navigate to File>Save Workspace as... and save the workspace to the vscodeworkspace folder that you previously created. In the future you can open this workspace file and it will open your project folders and set your configurations.

Now lets do a few setup tasks

  1. Open your .zshrc file. It is typically located in your users directory: /Users/andrewhunter. Note the .zshrc file is a hidden file so you can unhide files in a mac finder with "command" + "shift" + ".". Place the following somewhere in your .zshrc file. Note below that I am using my testenv, so you can replace the function name and testenv with whatever names you choose.
  2. activatetestenv(){
     source testenv/bin/activate
     export DJANGO_SETTINGS_MODULE=<your project name>.settings_development
     export SECRET_KEY=<your secret key found inside settings.py file>
    }

    deactivatemyvenv(){
     deactivate
     unset DJANGO_SETTINGS_MODULE
    }
  3. You should test your zshrc function in a command line. To check that it is working use: printenv to see the environment variables. You should see your DJANGO_SETTINGS_MODULE is set to your development settings file
  4. Open your settings.py file file and add the following import statments to the top of the file: import os and from decouple import config
  5. Update the secret key to the following: SECRET_KEY = config('SECRET_KEY') This allows us to remove our secret key from the settings.py file which will eventaully get loaded to our version control system.
  6. Create a copy of our django "settings.py" file and name it "settiings_development.py". The file should be located in the same location as settings.py. For this project we will maintain 2 settings files, one for the production environment (AWS) and the other for our local development. This may not be the most efficient way to organize the settings files, but it works for now.

Now start a django app. I prefer to keep things simple with a single app. There is an interesting debate here regarding the use of django apps.

  1. Make sure your pwd is the same directory where you manage.py file is located. And start the core app with the following command: python manage.py startapp <your app name, e.g. core>. Note, in this tutorial I only created a single app name core. You can rename this app whatever you'd like, but I will refer to "core" in the rest of the tutorial.
  2. Add your app name, e.g. 'core', to both settings.py and settings_development.py file:
  3. INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'core'
    ]
  4. Open the views.py file and update as shown:
  5. from django.shortcuts import render
    from django.http import HttpResponse

    def index(request):
     return HttpResponse("Hello, world. You're at the core index.")
  6. Add a file to the core app folder titled "urls.py"
  7. Update the core app urls.py file as below
  8. from django.urls import path

    from . import views

    urlpatterns = [
     path("", views.index, name="index"),
    ]
  9. Now open the urls.py file in the example_project folder and update as below
  10. from django.contrib import admin
    from django.urls import include, path

    urlpatterns = [
     path('admin/', admin.site.urls),
     path("", include("core.urls")),
    ]
  11. Test your app with: python manage.py runserver and you will see an output that looks like the following
  12. Watching for file changes with StatReloader
    Performing system checks...

    System check identified no issues (0 silenced).
    June 09, 2023 - 21:06:17
    Django version 4.2.1, using settings 'andyhunter.settings_development'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CONTROL-C.
  13. Copy http://127.0.0.1:8000/ into a web browser and you should see your hello world output

Update Django backend to use PostgreSQL

First we will create the database

  1. Open a terminal and check that postgres is installed with: which postgres
  2. Initiate a postgres shell session with: sudo -u postgres psql. If the shell initiates successfully you will see postgres=# at the start of your command line
  3. Create the database for your project with: CREATE DATABASE NAME <your_database_name>; Remember to end all your postgres shell commands with a ";"
  4. Update the DATABASES variable in settings_development.py file as follows:
  5. DATABASES = {

     'default': {
      'ENGINE': 'django.db.backends.postgresql',
      'NAME': <your_database_name>,
      'USER': config('LOCAL_POSTGRES_USER'),
      'PASSWORD': config('LOCAL_POSTGRES_PW'),
      'HOST': 'localhost',
      'PORT': '',
     }
    }
  6. Open your zshrc file and add your LOCAL_POSTGRES_USER and LOCAL_POSTGRES_PW as shown below:
  7. activatetestenv(){
     source testenv/bin/activate
     export DJANGO_SETTINGS_MODULE=<your project name>.settings_development
     export SECRET_KEY=<your secret key found inside settings.py file>
     export LOCAL_POSTGRES_USER=<your postgres username>
     export LOCAL_POSTGRES_PW=<your postgres password>
    }

    deactivatemyvenv(){
     deactivate
     unset DJANGO_SETTINGS_MODULE
    }
  8. Now activate your environment using the zshrc command: activatetestenv
  9. Check that your DJANGO_SETTINGS_MODULE variable is set with: printenv
  10. Make database migrations with: python manage.py makemigrations
  11. Migrate database with: python manage.py migrate You should receive terminal output that the migrations were performed successfully.
  12. Check that the database was created
    1. Login into shell usingsudo -u postgres psql or python manage.py dbshell
    2. If you used sudo, you connect to the database via: \c <database name>
    3. and list the databse relations with: \dt
    4. If you used manage.py, it will connect you to your database and you can list the relations with: \dt
  13. Optional: Create an admin superuser with: python manage.py createsuperuser
  14. Now you can check that the superuser was created by logging into your database via the previous mentioned commands. You can this list the elements in the "auth_user" table with: TABLE auth_user;

Update WebPage with Template from Bootstrap

We'll update the index.html file with borrowed code from Bootstrap

  1. First create a "templates/core" directory inside the core directory.
  2. Create a "static/core/css" directory inside the core directory.
  3. Download the templates from Bootstrap located here. In the blog folder there should be "index.html" and "blog.css" files.
  4. Place a copy of the "index.html" file into your core/templates/core directory
  5. Place a copy of the "blog.css" file into your core/static/core/css
  6. Edit the "index.html" file by removing: <link href="../assets/dist/css/bootstrap.min.css" rel="stylesheet"> and replacing with the following:
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  7. Edit the "index.html" file by removing: <link href="blog.css" rel="stylesheet"> and replacing with the following:
    {% load static %}
    <link rel="stylesheet" type="text/css" href="https://ah-staticfiles.s3.amazonaws.com/static/core/css/blog.css" />

AWS Deployment

AWS Tools and Resources Setup

We first start by creating an AWS account and S3 bucket for static file storage

  1. Create an amazon AWS account here. Unfortunately, I created my AWS account many years ago so I don't have much advice on creating an account. However, here are a few tips and lessons learned:
    • In the account development process keep an eye out for your AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID as these will be needed in a later step
    • AWS charges can be significant, tens-hundreds of dollars, so be cautious and do your research. This project should not incur significant charges (and should be free if you are still eligible for the 1 year free-tier resources), but you should always remember to terminate resources that you are not using.
    • If you are new to AWS and serious about using it, AWS Developer Support Plan has been a helpful resource when I run into issues. Their responses been very helpful.
    • AWS provides computer resources around the world, and thus when you work in AWS you will select a location (e.g. Northern California, us-west-q). I don't prescribe what AWS regions to use, however I recommend trying to stick to a single region for everything during these practice projects since some steps require that you have a consistent region selected.
    • AWS does not recommend using root access to perform developer related tasks in your AWS account, here is a link that provides guidance AWS Identity and Access Management (IAM).
  2. After you create your account, this would be a good time to work through the AWS Tutorial. Theres also a good django/python tutorial here.
  3. We will use AWS Elastic Beanstalk for deployment, which provides a command line tool (EB CLI). Follow the instructions here to download. I used the github EB CLI setup scripts which worked well. Note, the installation steps identify "virtualenv" as a pre-requisite however I'm unsure if this is necessary given we will be using the built in python venv.
  4. Create an amazon S3 bucket to store you website static files
    1. Navigate to the S3 console and select "Create Bucket"
    2. Provide a bucket name and use all default options except you will want to uncheck "Block all public access". Review the cautions, and be aware the data in the bucket may be publicly available.
    3. Now your bucket should appear in the list of buckets in the S3 homepage. Select your newly created bucket and go to the permissions tab
    4. On the permissions tab of your bucket, scroll down to the Bucket Policy section and select edit. Place the following code in the Policy text box:
    5. {
       "Version": "2012-10-17",
       "Statement": [
        {
         "Sid": "PublicReadGetObject",
         "Effect": "Allow",
         "Principal": "*",
         "Action": "s3:GetObject",
         "Resource": "arn:aws:s3:::<your bucket name>/*"
        }
       ]
      }

Recall we are maintaining 2 settings files. We will make some edits on settings.py because this will be the file that the deployment environment will look for by default. I believe this default setting is set in the manage.py file.

  1. Update the imports in your settings.py file with the following:
  2. from pathlib import Path
    import os
    from decouple import config
    import subprocess
    import ast
  3. In the settings.py file update the DEBUG=True to DEBUG=False
  4. Add the following toward the top of your settings.py file. This function will be used to access your environment variables in env file that we will create later. Note, this change and the following change are a workaround to access the environment variables, I got from the post here
  5. def get_environ_vars():
     completed_process = subprocess.run(
      ['/opt/elasticbeanstalk/bin/get-config', 'environment'],
      stdout=subprocess.PIPE,
      text=True,
      check=True
     )
     return ast.literal_eval(completed_process.stdout)
  6. In the settings.py file replace DATABASES = {} with the following:
  7. if 'RDS_HOSTNAME' in os.environ:
     DATABASES = {
      'default': {
       'ENGINE': 'django.db.backends.postgresql',
       'NAME': os.environ['RDS_DB_NAME'],
       'USER': os.environ['RDS_USERNAME'],
       'PASSWORD': os.environ['RDS_PASSWORD'],
       'HOST': os.environ['RDS_HOSTNAME'],
       'PORT': os.environ['RDS_PORT'],
     }
    }
    else:
     env_vars = get_environ_vars()
     DATABASES = {
      'default': {
       'ENGINE': 'django.db.backends.postgresql',
       'NAME': env_vars['RDS_DB_NAME'],
       'USER': env_vars['RDS_USERNAME'],
       'PASSWORD': env_vars['RDS_PASSWORD'],
       'HOST': env_vars['RDS_HOSTNAME'],
       'PORT': env_vars['RDS_PORT'],
     }
    }
  8. Comment out or remove STATIC_URL = 'static/' and replace with the following:
  9. AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID')
    AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY')
    AWS_STORAGE_BUCKET_NAME = '<your static file bucket name>'
    AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
    AWS_S3_REGION_NAME = '<your AWS region, e.g. us-west-1>'
    AWS_S3_OBJECT_PARAMETERS = {
     'CacheControl': 'max-age=86400',
    }
    AWS_LOCATION = 'static'

    STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
    STORAGES = {"staticfiles": {"BACKEND": "storages.backends.s3boto3.S3StaticStorage"}}
  10. Locate the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY that we were looking for during the AWS account setup, open your zshrc file and add the following statements (do not add it to either of the previously created zshrc functions, you can add it to the main body of the .zshrc file):
  11. export AWS_ACCESS_KEY_ID=<your aws access key id>
    export AWS_SECRET_ACCESS_KEY=<your aws secret access key>
  12. Open settings.py file and update your INSTALLED_APPS to add STORAGES, as follows:
  13. INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'core',
     'storages',
    ]

Now we are going to push our static files to the S3 bucket that we created.

  1. Recall that we should almost always be working in the terminal in our virtual environment and with our django settings file set to: DJANGO_SETTINGS_MODULE=<your project name>.settings_development, which was activated with our previously defined .zshrc function title activatetestenv(). In this step we will temporarily reset our local environment variable to use the settings.py file. You do this by typing the following into your terminal: export DJANGO_SETTINGS_MODULE=<your project name>.settings. Note, I think we could also use the deactivatetestenv() .zshrc function that we previously created, which unsets the variable, thus django will resort to the default settings.py file for any operations
  2. Now in your terminal (make sure your pwd is location where manage.py file is located) type in the following: python manage.py collectstatic. Based on the settings.py file, it will push the static files in your project to the S3 bucket. You should get a confirmation of the files added in the terminal output. You can now go to your AWS bucket and see that the files have been loaded to the storage service. Congrats! You completed your first significant AWS operation.
  3. After we complete the above step we will always re-activate our environment with zshrc function: activatetestenv. Note, activatetestenv is just the name of the function i have used, replace this with your zshrc function name. This will set our settings back to the development settings. And at this point might be worth checking that your development environment still works with: python manage.py runserver

This is a good time to commit our code to a github repo

  1. Create a github account here
  2. Create a repository. There are some instructions here. You can use all the default options, but for this tutorial do not add a readme or a gitignore file, we will add them next
  3. Open your project code directory (the one that contains the manage.py file) in finder or a terminal and create a "README.md" file and add the following to the file: # Project Name
  4. To the same directory add a file named ".gitignore". The gitignore file tells git what files to exclude from a push to the remote github repository. Add the following contents to the file:
  5. .DS_Store
    __pycache__/

    # images
    core/static/core/images/

    # virtual environment
    <your virtual env name, e.g. testenv>/

    # vscode
    .vscode/*
    !.vscode/settings.json
    !.vscode/tasks.json
    !.vscode/launch.json
    !.vscode/extensions.json
    !.vscode/*.code-snippets

    # Local History for Visual Studio Code
    .history/

    # Built Visual Studio Code Extensions
    *.vsix

    # Elastic Beanstalk Files
    .elasticbeanstalk/*
    !.elasticbeanstalk/*.cfg.yml
    !.elasticbeanstalk/*.global.yml

    # ignore .env which holds keys
    .env
  6. Now go to the terminal, and make sure you are in the project directory which contains your manage.py file. This is your code directory and contains the files that we want to version control. Enter: git init into the terminal. You should notice a new .git directory was created.
  7. This step is personal preference but let's rename our primary branch to "main" by entering the following into the terminal (you can name it whatever you'd like): git branch -m main
  8. Lets add all the files (except those identified in the gitignore) to the repo: git add .
  9. Commit the files and add a commit message: git commit -am 'first commit'
  10. Now we will set the remote repo location to the github repo that we created in a previous step. Go to your github repo and locate the url for your repo. You should find it under the "Code" dropdown menu. Copy the url for use in the next step.
  11. Set your remote repo url with: git remote add origin <your github remote repo url>
  12. Now push your committed files to the remote repo with: git push -u origin main. Note, replace main with whatever branch name you selected previously.

Now lets continue with some prep steps

  1. First create a .ebignore file in same location as our .gitignore. Add the following contents to the file. Note, the only difference is that we have removed the .env from the ebignore because the AWS environment will utilize the .env file for access/keys.
  2. .DS_Store
    __pycache__/

    # images
    core/static/core/images/

    # virtual environment
    <your virtual env name, e.g. testenv>/

    # vscode
    .vscode/*
    !.vscode/settings.json
    !.vscode/tasks.json
    !.vscode/launch.json
    !.vscode/extensions.json
    !.vscode/*.code-snippets

    # Local History for Visual Studio Code
    .history/

    # Built Visual Studio Code Extensions
    *.vsix

    # Elastic Beanstalk Files
    .elasticbeanstalk/*
    !.elasticbeanstalk/*.cfg.yml
    !.elasticbeanstalk/*.global.yml
  3. Now create a directory at the same level as your manage.py file called ".ebextensions"
  4. In the .ebextensions direcotry create a file called "django.config" and add the following contents to the file. Note the Resources code helped with redirecting code from http to https. The commands code helped with fixing 4xx errors in AWS. (reminder to Andy, see AWS ticket for my info)
  5. option_settings:
     aws:elasticbeanstalk:container:python:
      WSGIPath: <your project name>wsgi:application
    Resources:
     AWSEBV2LoadBalancerListener:
      Type: AWS::ElasticLoadBalancingV2::Listener
      Properties:
       LoadBalancerArn:
        Ref: AWSEBV2LoadBalancer
       Port: 80
       Protocol: HTTP
       DefaultActions:
        - Type: redirect
         RedirectConfig:
          Host: "#{host}"
          Path: "/#{path}"
          Port: "443"
          Protocol: "HTTPS"
          Query: "#{query}"
          StatusCode: "HTTP_301"
    commands:
     01-ec2-ip:
      command: |
       TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
       EC2_IP=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/local-ipv4/`
       echo $EC2_IP > /tmp/ec2_ip.txt
  6. Now add a ".env" file in the same folder as your settings.py file. In this file add the followings contents:
  7. SECRET_KEY=<your secret key >
    AWS_ACCESS_KEY_ID=<your aws access key id>
    AWS_SECRET_ACCESS_KEY=<your aws secret access key>
  8. Add the following code to your settings.py file PRIOR to the defintion of ALLOWED_HOSTS
  9. def get_ec2_ip():
     with open('/tmp/ec2_ip.txt') as f:
      IP = f.read()
      print(IP)
     return IP
    EC2_IP = get_ec2_ip()
  10. Add the following code to your settings.py file AFTER the definition of ALLOWED_HOSTS
  11. ALLOWED_HOSTS.append(EC2_IP.strip('\n'))
  12. At this point it would be good to check that your "requirements.txt" is still up to date with your virtual environment. Activate your virtual environment and check the isntalled pacakges with: pip freeze to ensure the output is still the same as the "requirements.txt" file. If not you should update youre "requirements.txt" file.

Now we will start deploying

  1. Let's start by setting up a keypair that we'll use to SSH into the EC2 instance. Follow the directions here to create the keypair, which will result in an automatic download of a pem file containing the credentials. After Step 9 in provided link, move your pem file to your users/<your mac userfolder name>/.ssh directory and then proceed with the Step 10 chmod.
  2. Test the status of the elastic beanstalk environment by tpying: eb status in the terminal. You should receive an error stating that directory has not been set up with an environment. You can use this command in the future to get status on your environment.
  3. Type the following into your terminal: eb init. This command will walk you through several steps
    1. Select your region
    2. Enter an application name
    3. Are you using python? Select yes
    4. Python Version? We will use 3.11
    5. Use CodeCommit? Not for this tutorial
    6. It will ask you if you want to setup SSH - select yes
  4. Create the environment for your application. There are two options:
    1. Run the following: eb create <your env name> –-service-role=arn:aws:iam::<your AWS account number>:role/service-role/aws-elasticbeanstalk-service-role. Note this may take a few minutes. If you get any warnings, wait for completion of the activity in the terminal and then navigate to your environment in the elastic beanstalk console. Go to the logs tab and review logs for cause of the error.
    2. The service role option in the previous step was needed due to an issue with the way AWS names this particular service role. An alternative is to delete the service role titled "aws-elasticbeanstalk-service-role". Then you can run eb create <your env name> (Reminder to Andy to look at the AWS ticket for detail on this issue)
  5. If you need to restart the process, here is the AWS direction on the order in which you should delete/cleanup your applications and environments
  6. Type eb status in the terminal. It will return some information about your environment.
  7. Copy CNAME value to your clipboard. Open your "settings.py" file and add the CNAME value to your allowed hosts as follows: ALLOWED_HOSTS = ['<your environment CNAME>']
  8. This is a good time to commit your files to git. I always run a git add . then git commit and lastly a git push

Now we are going to add the database to an AWS RDS instance

  1. Go to your newly created elastic beanstalk environment in the AWS console. Select the configuration tab on the left hand side, and go to Networking, Database, and Tags and select edit. Follow the below steps to create your database (it may take up to 10 minutes):
    1. Click enable database
    2. Snapshot: none
    3. Engine: Postgres
    4. Version: 15.2 or whatever you are using
    5. DB instance class: db.T3.micro
    6. Storage: 5GB
    7. Set your database username and password
  2. You can now check the RDS console to see that there is 1 database instance.

Finally its time to deploy code to our elastic beanstalk environment

  1. Make sure that you activated your virtual environment. In the terminal enter: eb deploy
  2. After the terminal is complete with the deploy task. We will SSH into the EC2 instance to migrate the database. To ssh, enter: eb ssh into the terminal.
  3. The first time you SSH into the EC2 instance you may get a message that looks like the below. It is safe to say yes to this prompt.
  4. The authenticity of host 'IP_ADDRESS_HERE (IP_ADDRESS_HERE)' can't be established.
    AB12345 key fingerprint is SHA256:randomcharactershere.
    This key is not known by any other names
    Are you sure you want to continue connecting (yes/no/[fingerprint])?
  5. Navigate to the var/app directory to activate your virtual environment
  6. Enter: ls to see the list of files and directories. It should returen a "current" and "venv" folder. The "current" folder holds your application files and the "venv" folder holds your virtual environment.
  7. Navigate into the "venv" folder and into "staging-LQM1lest" folder. Now lets activate your virtual environment in the EC2 instance with: source bin/activate. You should now see "staging-LQM1lest" at the beginning of your command line.
  8. Now navigate to where your manage.py file is located in the "var/app/current" folder.
  9. Run the makemigrations command with: python3 manage.py makemigrations
  10. Run the migrate command with: python3 manage.py migrate
  11. You can exit your SSH session with: exit
  12. In the terminal enter: eb open . SUCCESS! You've deployed your first application with Elastic Beanstalk

Now we link our elastic beanstalk environment to a route 53 domain - note, if you don't have a route 53 domain, the rest of this tutorial is not relevant, but you've already finished the hard part. Congrats!

  1. Go to AWS route 53 console. Click Hosted Zones and choose Create Record. Select the following options:
    1. Leave subdomain blank
    2. leave record type as is
    3. Select alias
    4. Select alias to elastic beanstalk environment
    5. Select the region - the same as which you've been using previously
  2. Creat another record, but this time add "www" to subdomain
  3. Update our "settings.py" file to allow for the above records to host our application. Update the allowed hosts as follows:
  4. ALLOWED_HOSTS = ['<your environment CNAME >',
    '<your domain name record, e.g. example_project.com >',
    '<your domain name record, e.g. www.example_project.com>',
    '127.0.0.1',
    'localhost' ]
  5. Commit your changes to git and github
  6. Deploy your app again with: eb deploy
  7. Now try typing in your domain name into a web browser. You have your first website, utilizing your first domain name!

You'll notice that your domain is using http, and thus insecure. The next steps will set up your domain/environment to use a secure https connection

  1. First request a certificate by following the instructions here.
  2. Next perform the DNS validation instructions here.
  3. Lastly configure a listener for elastic beanstalk as shown here.

Now lets describe our CI/CD process

  1. Make updates to your app
  2. Commit changes to git and github
  3. The followings steps are only required if you made edits to your static files
    1. Enter the following into the terminal: export DJANGO_SETTINGS_MODULE=<your project name>.settings
    2. Temporarily comment out the followings sections from the "settings.py" file
    3. def get_ec2_ip():
       with open('/tmp/ec2_ip.txt') as f:
        IP = f.read()
        print(IP)
       return IP
      EC2_IP = get_ec2_ip()

      ALLOWED_HOSTS.append(EC2_IP.strip('\n'))

      def get_environ_vars():
       completed_process = subprocess.run(
        ['/opt/elasticbeanstalk/bin/get-config', 'environment'],
        stdout=subprocess.PIPE,
        text=True,
        check=True
       )

       return ast.literal_eval(completed_process.stdout)

      if 'RDS_HOSTNAME' in os.environ:
       DATABASES = {
        'default': {
         'ENGINE': 'django.db.backends.postgresql',
         'NAME': os.environ['RDS_DB_NAME'],
         'USER': os.environ['RDS_USERNAME'],
         'PASSWORD': os.environ['RDS_PASSWORD'],
         'HOST': os.environ['RDS_HOSTNAME'],
         'PORT': os.environ['RDS_PORT'],
        }
       }

      else:
       env_vars = get_environ_vars()
       DATABASES = {
        'default': {
         'ENGINE': 'django.db.backends.postgresql',
         'NAME': env_vars['RDS_DB_NAME'],
         'USER': env_vars['RDS_USERNAME'],
         'PASSWORD': env_vars['RDS_PASSWORD'],
         'HOST': env_vars['RDS_HOSTNAME'],
         'PORT': env_vars['RDS_PORT'],
        }
       }
    4. Type the following into the terminal: python manage.py collectstatic.
    5. Now reactiveate your local development environment so you don't forget later with the zshrc function you created earlier
    6. If you commented out the above code in "settings.py" go back into the file and uncomment what you previously commented out
    7. Since you are have reverted back to the code that was committed to your git repo, your git status command should reveal that everything is up-to-date
  4. Enter: eb deploy into the terminal. And check that your website has the updates that you made. I thought this CI/CD process (if you can call it that) was pretty efficient!
  5. You can also check that your local development environment still works by resetting your django settings module local environment variable with activatetestenv and then running your local deployment command: python manage.py runserver