Deploying to Platform.sh

The third edition of Python Crash Course walks readers through the process of deploying the Learning Log project to Platform.sh, a hosting site that still offers a free trial. As of this writing, the free trial lasts for 30 days and does not require a credit card when you sign up. You are limited to two apps in a 24-hour period while on the free trial.

The description of the deployment process shown here is not as thorough as what you’d find in the third edition, but should be clear enough to make a successful deployment if you’ve finished the Learning Log project through the first half of Chapter 20 in the second edition.


Make a Platform.sh account

Go to the Platform.sh home page and make an account.

When dealing with hosting services, you are entirely responsible for your bill. Platforms sometimes change their plans with no notice, so the only document that really matters is your invoice. You should verify everything you see here and anywhere else online against what you see in your Platform.sh dashboard. This is not unique to Platform.sh; this applies to any hosting service that you sign up for. It’s also possible to create resources that are much more expensive than you intend, and only find out after you’ve incurred significant charges. Keep this in mind if you keep your account active long enough to put a credit card on file.

Install the Platform.sh CLI

Visit The Platform.sh CLI documentation page for instructions about how to install the Platform.sh CLI on your system.

Note: On Windows, you’ll need to use the Windows Subsystem for Linux (WSL) a Git Bash terminal, or another Bash-compatible terminal. See Installing the Platform.sh CLI on Winodws.

Install platformshconfig

The platformshconfig package helps detect whether the project is running on your local system, or on a Platform.sh server. It also handles some configuration work on the remote server. Use pip to install it:

(ll_env)learning_log$ pip install platformshconfig

Create a requirements.txt file

Generate a requirements.txt file with the following command:

(ll_env)learning_log$ pip freeze > requirements.txt

This looks at all the packages that have been installed to support the Learning Log project, and makes sure Platform.sh will install the same versions of those packages on its servers. You can open this file and see exactly which versions will be installed:

asgiref==3.5.2
beautifulsoup4==4.11.1
Django==4.1.3
django-bootstrap4==22.3
platformshconfig==2.4.0
soupsieve==2.3.2.post1
sqlparse==0.4.3

These are the most up to date versions at the time of this writing. If you see different versions in your output, you should keep those versions.

Add gunicorn and psycopg2

The remote server will require two additional packages, that you don’t need to install locally. Make a new file called requirements_remote.txt, and add the following two packages to it:

# Requirements for live project.
gunicorn
psycopg2

The gunicorn package handles live requests just like runserver does locally, but it can handle multiple simultaneous requests. The pscyopg2 package helps Django manage the Postgres database that Platform.sh uses.

Add configuration files

Platform.sh deployments need two configuration files:

  • .platform.app.yaml This is the main configuration file for the project.
  • .platform/services.yaml This file defines additional services the project needs.

The first is a single file that needs to start with a dot. The second is a file called services.yaml, in a folder called .platform. Files and folders that start with a dot are called dotfiles, or hidden files, because they’re often hidden by file browsers.

Making hidden files visible

You may need to make some changes to see hidden files and folders on your system:

  • On Windows, open Windows Explorer, and then open a folder such as Desktop. Click the View tab, and make sure File name extensions and Hidden items are checked.
  • On macOS, you can press Cmd-Shift-. in any file browser window to see hidden files and folders.
  • On Linux systems such as Ubuntu, you can press Ctrl-H in any file browser to display hidden files and folders. To make this setting permanent, open a file browser such as Nautilus and click the options tab (indicated by three lines). Select the Show Hidden Files checkbox.

The .platform.app.yaml file

This is the longer configuration file, because it controls the overall deployment process. Open a new window in an editor, paste the following into it, and save it as .platform.app.yaml:

name: "ll_project"
type: "python:3.10"

relationships:
    database: "db:postgresql"

# The configuration of the app when it's exposed to the web.
web:
    upstream:
        socket_family: unix
    commands:
        start: "gunicorn -w 4 -b unix:$SOCKET learning_log.wsgi:application"
    locations:
        "/":
            passthru: true
        "/static":
            root: "static"
            expires: 1h
            allow: true

# The size of the persistent disk of the application (in MB).
disk: 512

# Set a local read/write mount for logs.
mounts:
    "logs":
        source: local
        source_path: logs

# The hooks executed at various points in the lifecycle of the application.
hooks:
    build: |
        pip install --upgrade pip
        pip install -r requirements.txt
        pip install -r requirements_remote.txt
        
        mkdir logs
        python manage.py collectstatic
        rm -rf logs
    deploy: |
        python manage.py migrate

You don’t need to understand everything here, but you should skim it and get an idea of what it’s doing for you. It specifies the name of the project, and the version of Python we’re using. The Platform.sh documentation has a list of currently supported Python versions.

The relationships section defines other services the project needs, in this case a database. The web section tells the server how to respond to incoming requests for specific pages, and resources needed to build pages.

We’re requesting 512MB of disk space, and we’re setting up a place for log files. Finally, in the hooks section, we tell the server which packages to install (from the requirements.txt and requirements_remote.txt files). We also run the migrate command during the deployment process.

The .platform/services.yaml file

Make a new folder called .platform, in the same directory as manage.py. Make sure you include the dot at the beginning of the name. Inside that folder, make a file called services.yaml and enter (or paste) the following:

# Each service listed will be deployed in its own container as part of your
#   Platform.sh project.

db:
    type: postgresql:12
    disk: 1024

This file defines one service, a Postgres database.

Modify settings.py for Platform.sh

Copy and paste the following section into the end of settings.py, (don’t copy the --snip--):

--snip--

# Platform.sh settings.
from platformshconfig import Config

config = Config()
if config.is_valid_platform():
    ALLOWED_HOSTS.append('.platformsh.site')
    DEBUG = False

    if config.appDir:
        STATIC_ROOT = Path(config.appDir) / 'static'
    if config.projectEntropy:
        SECRET_KEY = config.projectEntropy

    if not config.in_build():
        db_settings = config.credentials('database')
        DATABASES = {
            'default': {
                'ENGINE': 'django.db.backends.postgresql',
                'NAME': db_settings['path'],
                'USER': db_settings['username'],
                'PASSWORD': db_settings['password'],
                'HOST': db_settings['host'],
                'PORT': db_settings['port'],
            },
        }

This imports the platformshconfig package, and then makes configuration changes that are specific to the deployed version of the project. It allows the project to be served by hosts ending in .platformsh.site, handles static files correctly, and configures the SECRET_KEY and DATABASE settings.

Use Git to track the project

Git is a source code management tool. Git is used for almost all deployment workflows, because you can make “snapshots” of your projects called commits. If your deployment stops working, you can use Git to revert back to the last working commit of your project, and avoid significant downtime in your projects.

Installing Git

Check if Git is already installed on your system:

(ll_env)learning_log$ git --version
git version 2.30.1

If you need to install Git on Windows or macOS, go to Git’s website to download an installer. On apt-based Linux systems, the command sudo apt install git-all should work.

Configuring Git

If this is the first time you’re using Git, you’ll need to set your username and email:

(ll_env)learning_log$ git config --global user.name "username"
(ll_env)learning_log$ git config --global user.email "username@example.com"

You can use your actual email address, or an address ending in example.com.

Make a .gitignore file

Git shouldn’t track every file in the project, so we’ll write a .gitignore file telling Git which files it doesn’t need to track:

ll_env/
__pycache__/
*.sqlite3

We don’t want to push the virtual environment, Python’s cached files, or the local database. If you’re on macOS, you can add .DS_Store to this file as well.

Make sure you save this file with a dot in front of it; it needs to be called .gitignore, not gitignore. It also can’t have a file ending such as .txt.

Committing the project

Now we’ll commit the current state of the project:

(ll_env)learning_log$ git init
Initialized empty Git repository in /home/eric/pcc/learning_log/.git/

(ll_env)learning_log$ git add .
(ll_env)learning_log$ git commit -am "Ready for deployment to Platform.sh."
[main (root-commit) 79fef72] Ready for deployment to Platform.sh.
 45 files changed, 712 insertions(+)
 create mode 100644 .platform.app.yaml
 create mode 100644 .platform/services.yaml
 create mode 100644 requirements_remote.txt
--snip--
create mode 100644 users/views.py

(ll_env)learning_log$ git status
On branch main
nothing to commit, working tree clean

There are four commands used here:

  • git init initializes Git; it sets Git up to track changes in this project.
  • git add . tells Git to start tracking every file in this project that’s not listed in .gitignore.
  • git commit -am "commit_message" tells Git to take a snapshot of the project, and record the given commit message.
  • git status This verifies that the commit was successful. When you run this command, you should see the brach you’re currently working on, and the message that there’s nothing to commit and that the working tree is “clean”.

You may see a different main branch name, such as master or trunk.

Creating a project on Platform.sh

The Learning Log project still runs locally, and is now configured for deployment to Platform.sh as well. Log in to the Platform.sh CLI in the terminal:

(ll_env)learning_log$ platform login
Opened URL: http://127.0.0.1:5000
Please use the browser to log in.
--snip--
Do you want to create an SSH configuration file automatically? [Y/n] Y

A browser tab will open where you can log in. Once you’re logged in you can close the browser tab. If you’re prompted about creating an SSH configuration file, enter Y so you can connect to the remote server later.

Now you can create a project. There are a few prompts that will come up. Start by issuing the platform create command:

(ll_env)learning_log$ platform create
* Project title (--title)
Default: Untitled Project
> ll_project

* Region (--region)
The region where the project will be hosted
  --snip--
  [us-3.platform.sh] Moses Lake, United States (AZURE) [514 gC02eq/kWh]
> us-3.platform.sh
* Plan (--plan)
Default: development
Enter a number to choose:
  [0] development
  --snip--
> 0

  * Environments (--environments)
  The number of environments
  Default: 3
> 3

  * Storage (--storage)
  The amount of storage per environment, in GiB
  Default: 5
> 5

Default branch (--default-branch)
The default Git branch name for the project (the production environment)
Default: main
> main

Git repository detected: /Users/eric/.../learning_log
Set the new project ll_project as the remote for this repository? [Y/n] Y

The estimated monthly cost of this project is: $10 USD
Are you sure you want to continue? [Y/n] Y

The Platform.sh Bot is activating your project
  ▀▄   ▄▀  
█▄█▀███▀█▄█
▀█████████▀
 ▄▀     ▀▄ 
           
  The project is now ready!

You’ll be prompted for a name to use for the deployed project, which is ll_project in the output shown here. Then you’ll be prompted to choose a region near you; for me that’s us-3.platform.sh. You’ll be prompted to choose a plan type, the number of environments, a storage amount, and the main branch name. The default options should work for all of these. You’ll need to answer Y to set the remote repository, and confirm what the cost of the plan would be if you continue to use it beyond the free tier.

You’ll see a dancing robot while your project is activated.

Pushing the project

The last step before seeing the live version of the project is to push your code to the remote server, using the platform push command:

(ll_env)learning_log$ platform push
Are you sure you want to push to the main (production) branch? [Y/n] Y
--snip--
The authenticity of host 'git.us-3.platform.sh (...)' can't be established.
RSA key fingerprint is SHA256:Tvn...7PM
Are you sure you want to continue connecting (yes/no/[fingerprint])? Y
Pushing HEAD to the existing environment main
  --snip--
  To git.us-3.platform.sh:3pp3mqcexhlvy.git
   * [new branch]      HEAD -> main

You’ll be asked for one more confirmation that you actually want to push the project. You might see a message about confirming th authenticity of Platform.sh as well.

After these final prompts, you should see a bunch of output scroll by as your code is copied to the server, and the project is built out on the remote server.

Note: If you’re on Windows and you see a popup stating “The authenticity of host host_address can’t be established”, enter yes. Deployment should continue after this.

Note: If realize you made a mistake such as a typo in a configuration file, fix the mistake and then reissue the git commit command. Then you can run platform push again. You shouldn’t need to repeat the platform create command, as that would just make an additional project on your Platform.sh account.

Viewing the project

You can see your project using the platform url command:

(ll_env)learning_log$ platform url
Enter a number to open a URL
  [0] https://main-bvxea6i-wmye2fx7wwqgu.us-3.platformsh.site/
  --snip--
> 0

The platform url command lists the URLs associated with a deployed project. Choose one, and your project should open in a new browser tab.

Note: When you deploy your project using a trial account, don’t be surprised if it sometimes takes longer than usual for a page to load. On most hosting platforms, free resources that are idle are often suspended and only restarted when new requests come in. Most platforms are much more responsive on paid hosting plans.

Creating a superuser

The database for the live project has been set up, but it’s completely empty. To create a superuser on the deployed project, we’ll start an SSH session where we can run management commands on the remote server:

(ll_env)learning_log$ platform environment:ssh

   ___ _      _    __                    _
  | _ \ |__ _| |_ / _|___ _ _ _ __    __| |_
  |  _/ / _` |  _|  _/ _ \ '_| '  \ _(_-< ' \
  |_| |_\__,_|\__|_| \___/_| |_|_|_(_)__/_||_|

   Welcome to Platform.sh.

web@ll_project.0:~$ ls
learning_logs ll_project logs manage.py requirements.txt
    requirements_remote.txt static users
web@ll_project.0:~$ python manage.py createsuperuser
Username (leave blank to use 'web'): ll_admin_live
Email address:
Password:
Password (again):
Superuser created successfully.
web@ll_project.0:~$ exit
logout
Connection to ssh.us-3.platform.sh closed.
(ll_env)learning_log$

When you first run the command platform environment:ssh, you may get another prompt about the authenticity of this host. If you see this, enter Y and you should be logged into a remote terminal session.

After running the ssh command, your terminal acts just like a terminal on the remote server. Your prompt will change to indicate that you’re in a web session associated with the project named ll_project. Issuing the ls command will list the files that were pushed to the remote server.

Issue the same createsuperuser command you used in Chapter 18. This time I used the name ll_admin_live, so I have a separate username that’s clearly associated with the deployed project. When you’re finished, enter exit to end the SSH session. You’ll see from the prompt that you’re back in your local system.

Note: Windows users will use the same commands shown here (such as ls instead of dir), because you’re running a Linux terminal through a remote connection.

Finishing Chapter 20

You can now go back to the book and pick up on page 458, at the Creating Custom Error Pages section. The only difference you’ll need to keep in mind is that you’ll use the commmand platform push when you see git push heroku main in the book.

Deleting a project on Platform.sh

When you’re finished with your deployed project, you can either keep it running with a paid plan, or destroy the project. To destroy the project, use the platform project:delete command:

(ll_env)learning_log$ platform project:delete

You’ll be prompted to confirm that you want to take this destructive acction. Respond to the prompts, and your project will be deleted.

You can also delete a project’s resources by logging in to the Platform.sh website and visiting your dashboard. This page lists all your active projects. Click the three dots in a project’s box, and click Edit Plan. This is a pricing page for the project; click the Delete Project button at the bottom of the page, and you’ll be shown a confirmation page where you can follow through with the deletion. Even if you deleted your project using the CLI, it’s a good idea to familiarize yourself with the dashboard of any hosting provider you deploy to.

Note: If you haven’t put a credit card on file, Platform.sh should just put your project to sleep when your free trial ends. But it’s a really good idea to destroy a test deployment if you’re not actively planning to continue maintaining it. It’s easy to end up with deployed projects that do start to accrue charges if you’re not careful.

The command platform create also set up a reference to the remote repository. You can remove this remote from the command line as well:

(ll_env)learning_log$ git remote
platform
(ll_env)learning_log$ git remote remove platform

The command git remote lists the names of all remote URLs associated with the current repository. The command git remote remove remote_name deletes these remote URLs from the local repository.