In any development project, you usually have to worry about many components: databases, dependencies, configurations, network settings and so on. Things can get too complicated when you need to transfer your project to another colleague or, even worse, transfer the project to a production environment, and very bad things can happen… Those problems has been solved by some useful tools, such as those for creating and managing containers.
AppJail is the framework we will use to create a container with our project: an API using the FastAPI framework. By using this tool we ensure that we can share and reproduce our project to guarantee that it will run smoothly with virtually the same environment as on the host. As a bonus, the host will not be affected.
The project
We use the following structure to simplify the development of our project:
|
|
Note: All files mentioned in this document are relative to the project/
directory.
Now we create a app/requirements.txt
to put the dependencies that our project needs:
fastapi[all]
Note: For simplicitly, we will install FastAPI with optional dependencies and features, but you probably won’t need all of them in a production environment. See documentation for details.
Our app/main.py
is only a few lines long:
|
|
A simple hello world
is displayed each time the user makes an HTTP request with the GET method.
The Makejail
The next step is to create the Makejail.
INCLUDE gh+AppJail-makejails/python
We have included the Makejail from the centralized repository to install Python.
# Optional, see below for details.
INCLUDE options/network.makejail
Another Makejail with only the network options we use for this jail. The content is as follows:
ARG network
ARG interface=python
OPTION virtualnet=${network}:${interface} default
OPTION nat
Of course, we need the options/
directory beforehand (remember: relative to the project/
directory):
|
|
Another way to achieve the above is to define option by option on the command-line when building the jail as we will see later.
WORKDIR /app
COPY app/
Using WORKDIR
we create a directory named /app
inside the jail. As we have changed the working directory, COPY
is affected and the target directory is relative to that directory, so we can omit the second argument and the files in app/
directory will be copied from the host to the jail. Remember that the /
suffix is for copying all the files, not the directory itself.
CMD pw useradd -n pyapp -d /app -s /bin/sh
CMD mkdir -p /app
CMD chown -R pyapp:pyapp /app
We create a dedicated user named pyapp
to run the script with less privileges than root
.
PKG py%{PYTHON_MAJOR}%{PYTHON_MINOR}-pip
devel/py-pip
is not installed by default, so we install it. We can install pip explicitly with the corresponding python version, but the Makejail for Python provides some build arguments.
PKG py%{PYTHON_MAJOR}%{PYTHON_MINOR}-wheel
PKG rust
lang/rust
is required by a FastAPI dependency. It is recommended to install devel/py-wheel
when installing python dependencies.
USER pyapp
RUN pip install --user -r requirements.txt
We install the dependencies by running pip
using the dedicated user.
STAGE cmd
USER pyapp
WORKDIR /app
RUN /app/.local/bin/uvicorn main:app --reload --host 0.0.0.0
The cmd
stage is used to run the python script using appjail run
as we will se later. The working directory is reset with each stage, so we need to specify it again. USER
is also reset for each stage.
The command uvicorn main:app
refers to:
main
: the filemain.py
(the Python ‘module’).app
: the object created inside ofmain.py
with the lineapp = FastAPI()
.--reload
: make the server restart after code changes. Only use for development.--host
: as the default is127.0.0.1
, we need to change to the jail’s IP address, but for simplicity, we use0.0.0.0
to listen on all IP addresses of all interfaces.
All of the above instructions in a single file can be seen below:
INCLUDE gh+AppJail-makejails/python
# Optional, see below for details.
INCLUDE options/network.makejail
WORKDIR /app
COPY app/
CMD pw useradd -n pyapp -d /app -s /bin/sh
CMD mkdir -p /app
CMD chown -R pyapp:pyapp /app
PKG py%{PYTHON_MAJOR}%{PYTHON_MINOR}-pip
PKG py%{PYTHON_MAJOR}%{PYTHON_MINOR}-wheel
PKG rust
USER pyapp
RUN pip install --user -r requirements.txt
STAGE cmd
USER pyapp
WORKDIR /app
RUN /app/.local/bin/uvicorn main:app --reload --host 0.0.0.0
The product
Since we are going to use a virtual network configuration, we first need to create one:
|
|
All the requirements have been done correctly, so all we have to do is open a shell and run:
|
|
The command appjail makejail
refers to:
-j pyapp
: name of the jail. If not defined, a random name is chosen.--network development
: the virtual network to be used.
As mentioned in previous sections, we can specify option by option on the command-line instead of using options/network.makejail
. Of course, remove the instruction that includes that file if you want to use the command-line.
|
|
Once the above process is finished, we can run the API:
|
|
And we can make an HTTP request but we only need the jail’s IP address.
|
|
Simplify your life
There is a time when we develop a python project and a dependency requires another dependency to be compiled. This is the case for watchfiles
in the FastAPI framework, which requires lang/rust
to compile its stuff. This takes a long time depending on your hardware, but there is a simple way to spend less time.
We can use the precompiled binaries in the FreeBSD repositories if the dependencies are already ported, so we just have to change our Makejail a bit to do this job.
INCLUDE gh+AppJail-makejails/python
# Optional, see below for details.
INCLUDE options/network.makejail
WORKDIR /app
COPY app/
CMD pw useradd -n pyapp -d /app -s /bin/sh
CMD mkdir -p /app
CMD chown -R pyapp:pyapp /app
PKG py%{PYTHON_MAJOR}%{PYTHON_MINOR}-fastapi
PKG py%{PYTHON_MAJOR}%{PYTHON_MINOR}-uvicorn
STAGE cmd
USER pyapp
WORKDIR /app
RUN uvicorn main:app --reload --host 0.0.0.0