Django Development Environment on Apple M1

The new Apple machines running M1 chips have a lot of buzz around them. The improved performance and battery life were the top two things that interested me the most. With the new ARM-based architecture there is some uncertainty over which things run natively or not. I wanted to find out for myself and document it here for reference.

Spoilers!

Aside from Homebrew saving files in a different location, everything else is basically the same compared to pre-M1 Macs. At the time of writing, Python, Homebrew, and the packages installed for the purpose of writing this guide all had M1-optimized versions.

Docker still has some known issues with M1 so we don’t include it in this guide.

Homebrew

Homebrew refers to itself as the missing package manager for macOS. You can think of it as the equivalent of apt or yum in the Linux world.

To install Homebrew on your new machine:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

It will then ask for your password so you can use sudo.

==> Checking for `sudo` access (which may request your password).
Password:

After asking for your password, it should show you what it plans to do:

==> This script will install:
/opt/homebrew/bin/brew
/opt/homebrew/share/doc/homebrew
/opt/homebrew/share/man/man1/brew.1
/opt/homebrew/share/zsh/site-functions/_brew
/opt/homebrew/etc/bash_completion.d/brew
/opt/homebrew
==> The following new directories will be created:
/opt/homebrew/bin
/opt/homebrew/etc
/opt/homebrew/include
/opt/homebrew/lib
/opt/homebrew/sbin
/opt/homebrew/share
/opt/homebrew/var
/opt/homebrew/opt
/opt/homebrew/share/zsh
/opt/homebrew/share/zsh/site-functions
/opt/homebrew/var/homebrew
/opt/homebrew/var/homebrew/linked
/opt/homebrew/Cellar
/opt/homebrew/Caskroom
/opt/homebrew/Frameworks
==> The Xcode Command Line Tools will be installed.

Press RETURN to continue or any other key to abort

On Apple Silicon, Homebrew will save its files in /opt/homebrew/ while in Intel-based Macs it will save its files in /usr/local/

It will then warn you that /opt/homebrew/bin/ is not in the $PATH environment variable. It will suggest entering the following in your shell:

echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

Confirm that this works by printing out the value of $PATH:

echo $PATH

On a completely fresh machine it should look something like this:

/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

Python

Homebrew has a dedicated page on how it interacts with Python with a good portion of it covering pip. This next section will cover just enough to get you something usable.

brew install [email protected]

The command above will place the files inside /opt/homebrew/opt/[email protected]/ so make sure /opt/homebrew/opt/[email protected]/bin/ is in your $PATH

Installing a different version of Python will save it in the appropriate location. Example: Installing Python 2.7 will save the files in /opt/homebrew/opt/[email protected]/ which means /opt/homebrew/opt/[email protected]/bin/ needs too be in your $PATH.

If you had to add this to ~/.zprofile or ~/.zshrc just now, don’t forget to use the source command on these files so you can pick up the change to $PATH. A simpler alternative would be to open a new shell session and continue the setup from there.

pip

Starting with Python 3.4, pip was bundled together with Python so we no longer have to do the usual song and dance where we had to use easy_install to install pip.

python3 -m pip install --upgrade --user setuptools
python3 -m pip install --upgrade --user pip

The two commands give you the latest versions of setuptools and pip in ~/Library/Python/3.8/bin/ instead of /usr/bin/python.

I prefer te separate Python into three separate levels:

virtualenv

Before we can create a new virtual environment using virtualenv, we need to install it using pip first since it’s a 3rd party package;

pip install virtualenv

This adds the package to ~/Library/Python/3.8/lib/python/site-packages/ and the executable to ~/Library/Python/3.8/bin/ so now we can create the virtual environment.

virtualenv --python=2.7 ve

The invocation above creates a directory named ve for our new virtual environment. The --python=2.7 part tells virtualenv to use Python 2.7 for this virtual environment.

Python 2.7 is no longer supported so this was just an example. To create a new virtual environment that uses Python 3.8 we do the following:

virtualenv --python=3.8 ve3

It’s important to use a different name or delete the ve directory. If we use the same name, it will just add the new files to the existing directory.

git

We don’t have to install git explicitly, because it now comes with macOS. It’s unlikely that it will be updated as often as the Homebrew version so we can install it using Homebrew as well.

brew install git

While we’re here, let’s generate a key pair so we can authenticate with a git hosting service like GitHub or GitLab without entering our passwords.

ssh-keygen -t rsa -b 4096

When asked where to save the private key it’s generally okay to use the default ~/.ssh/id_rsa. A lot of programs automatically use that key if it exists and if the user does not explicitly set a location. I usually use a different filename for the private key but I still save it inside ~/.ssh/ and ~/.ssh/id_rsa is a symbolic link to the actual private key.

It’s a good idea to use a strong one when asked for a passphrase for the private key. This adds an extra layer of security if another person or system is able to get a copy of your private key.

After ssh-keygen is done generating your key pair, we’ll be able to see a public key and a private key with a key size of 4096 bits inside ~/.ssh

Postgres

At the time of writing the latest major version is Postgresql 12.

If we want to install the latest version, we can do this:

brew install postgresql

It’s also possible to explicitly request for a version. For example if we want to install Postgresql 11, we can do this:

brew install postgresql@11

It won’t automatically run in the background as a service so we need to do this:

brew services start postgresql@11

This also automatically runs your Postgres service immediately after restarting your computer.

psycopg2

Django and Postgres usually go together and that means we need to install psycopg2 as a dependency.

Since we’re creating a development environment, I find it more convenient to use psycopg2-binary instead of using psycopg since it’s supposed to install a pre-compiled version of the package. This is true on my Linux machine, but this isn’t the case on macOS 11.2.3 and psycopg2-binary before 2.9.0

This section is dedicated to setting up psycopg that’s older than 2.9.0.

The first error you’ll encounter when trying to install psycopg2 or psycopg2-binary is this:

Error: pg_config executable not found.

pg_config is required to build psycopg2 from source.  Please add the directory containing
pg_config to the $PATH or specify the full executable path with the option:

    python setup.py build_ext --pg-config /path/to/pg_config build ...

or with the pg_config option in 'setup.cfg'.

If you prefer to avoid building psycopg2 from source, please install the PyPI 'psycopg2-binary' package instead.

For further information please check the 'doc/src/install.rst' file (also at <https://www.psycopg.org/docs/install.html>).

To get around this, we need to make pg_config discoverable in our $PATH

export PATH=/opt/homebrew/opt/postgresql@11/bin:$PATH

This also adds common Postgres tools to your $PATH like psql and pg_dump.

After updating your $PATH, this will be the next error that you encounter:

ld: warning: directory not found for option '-L/usr/local/opt/[email protected]/lib'
ld: warning: directory not found for option '-L/usr/local/opt/[email protected]/lib'
ld: warning: directory not found for option '-L/usr/local/opt/icu4c/lib'
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: command 'clang' failed with exit status 1

We get around this by setting our LDFLAGS and CPPFLAGS.

export LDFLAGS="-L/opt/homebrew/opt/[email protected]/lib -L/opt/homebrew/opt/[email protected]/lib"
export CPPFLAGS="-I/opt/homebrew/opt/[email protected]/include"

Final thoughts

Hopefully you have a working development environment after following this guide. This covers the most common stuff and it should be easy to figure out the other packages or services that you might need.

While my new M1 machine feels very snappy, it could be because it’s new and there’s not a lot of things running right now. What’s undeniably better though is the battery life which lasted at least 15 hours while I did the reading, writing, and installing the things mentioned in this post.

If you found this useful and would like to get updates on new posts, please subscribe to our newsletter and follow us on Twitter @DjangoCookbook.