Deploy a web app
Overview
Nowadays, most content served to users via the web is dynamic and via web applications. This tutorial will show you how to set up a simple web application with Flask in Python, but the process is much the same for any other web framework and we give tips for other languages.
Introduction
The SRCF hosts a general purpose web server that can serve all types of content. While the existence of a public_html
folder might lead users to think that only static content can be served, Apache’s .htaccess
file allows for arbitrary reverse proxying to a TCP port or UNIX socket. This gives users great freedom to do whatever they wish, including hosting applications written in popular frameworks like Django, Flask, Sinatra, Ruby on Rails, Express, etc.
/public/societies/sample
.
Pre-requisites
Before starting,
- ensure that you are comfortable in some capacity with the terminal
- ensure that you are performing any subsequent commands on our web server,
webserver.srcf.net
Environments
Python: set up a venv
You will want to deploy your application in a Python venv
so that you
can easily install and manage dependencies and versions.
A venv
is a
virtual Python environment that contains its own Python binary as well
as all of your dependencies. When you activate your venv
, PATH variables
like python
and pip
will use the correct versions specified when you
created your venv
.
-
Create a directory for your app to live in:
mkdir -p ~/myapp cd ~/myapp
-
Set up a venv
python3 -m venv venv
-
Activate the venv
. venv/bin/activate
You should do this step every time before running your app or managing installed packages.
-
Done! Your Python
venv
is now installed and hooked into your shell.
Node: installing nvm
You will want to deploy your application using nvm
so that you can
easily install and manage dependencies and versions.
nvm
stands for Node Version Manager. Ubuntu only provides a
significantly outdated version of Node.js (v4.2.6 at the time of
writing) in its repositories. Using nvm
allows you to choose any
version of Node to use in your environment. Note that it is your
responsibility to keep your node installations and nvm
itself updated.
-
Create a directory for your app to live in:
mkdir -p ~/myapp cd ~/myapp
-
Install
nvm
in your home directory. You’ll need to find the latest version from the NVM GitHub, and copy the one-liner straight into your shell to install it. At the time of writing, it looks like this:curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
Piping a script straight into your shell is potentially dangerous as you are allowing arbitrary code to be executed. Make sure you trust any script you run or vet it beforehand.Make sure you read the installation instructions, then go ahead and run it, and close/re-open your terminal as it suggests.
-
Install whatever version of
node.js
you want.nvm install 12 nvm alias default 12
-
Done! The version of
node.js
you specified is now installed and hooked into your shell.
Ruby: install rbenv
You will want to deploy your application using rbenv
so that you can
easily install and manage dependencies and versions.
-
Create a directory for your app to live in:
mkdir -p ~/myapp cd ~/myapp
-
Install
rbenv
in your home directory:git clone https://github.com/rbenv/rbenv.git ~/.rbenv cd ~/.rbenv && src/configure && make -C src echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc ~/.rbenv/bin/rbenv init
-
Follow the printed instructions on appending to your
~/.bashrc
file:echo 'eval "$(rbenv init -)"' >> ~/.bashrc
-
Install
ruby-build
as a plugin:mkdir -p "$(rbenv root)"/plugins git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
-
Install whichever version of Ruby you want.
rbenv install 2.6.6 rbenv local 2.6.6
-
Done! You now have a working Ruby installation that’s hooked into your shell.
Install your web framework
You should not rely on the system-wide versions of web frameworks present on our webserver as they are LTS releases and may be out of date. Instead, use the version manager that comes with your framework/language to install a more up to date version.
- Node:
npm i express
- Python:
pip install flask
orpip install django
- Ruby:
gem install rails
Create your app
If you have an existing node.js
app you can simply copy the code to a
directory of your choosing, ~/myapp/src
for example. You can use
scp/sftp
to upload it or clone it using git
or some other source
control tool.
If you’re new to web apps, though, it’s recommended you follow the getting started guide (or equivalent) for your language/framework of interest:
- Node: https://nodejs.org/en/docs/guides/getting-started-guide/
- Express: http://expressjs.com/en/starter/installing.html
- Ruby on Rails: https://guides.rubyonrails.org/getting_started.html
- Flask: https://flask.palletsprojects.com/en/1.1.x/quickstart/
We do have a few lines of code in a few languages that can springboard your app below. For real examples, you should visit the content of the sample
group at /public/societies/sample/
Flask
There is a demo flask app setup at https://sample.soc.srcf.net/flask/.
If you’re logged in via ssh, you can find the code at
/public/societies/sample/flask/app.py
.
Django
To create a skeleton Django project:
django-admin startproject example
mv example python
cd python
Now take a look at django/example/settings.py
for how to configure
your site. There is also a demo available at
https://sample.soc.srcf.net/django/.
Ruby on Rails
Ruby on Rails is arguably one of the most popular and influential web frameworks. To create a skeleton Rails project:
gem install rails
rails new myapp
You should now have a new Ruby on Rails project setup in the myapp
directory.
Sinatra
Sinatra is a lightweight web microframework that’s well suited to
simple projects. To install it run gem install sinatra
and then put
the following in myapp.rb
:
require 'sinatra'
get '/' do
'Hello world!'
end
You can then run this by typing ruby myapp.rb
and going to
http://localhost:4567 in your web browser. For futher information See
the Sinatra documentation.
Express
Sample site at https://sample.soc.srcf.net/nodejs/
and see nodejs/app.js
for a minimum base application.
Deploy your app
In a production environment, your app will need to “boot” (ie. start up) in a well-controlled manner. You will also want it to run in the background and ensure it restart when our web server reboots. systemd
is a popular choice for “supervising” a service.
Boot scripts
First create a bash script that will run your web app at
~/myapp/run.sh
with the following content:
Python
#!/bin/bash -e
. ~/myapp/venv/bin/activate
exec gunicorn -w 2 -b unix:/home/crsid/myapp/web.sock \
--log-file - main:app
Replace main:app
with the module containing the app, and name of your
app.
Node
#!/bin/bash -e
USER="$(whoami)"
[ -e "/home/crsid/myapp/web.sock" ] && rm "/home/crsid/myapp/web.sock"
umask 0
. ~/.nvm/nvm.sh
NODE_ENV=production PORT="/home/crsid/myapp/web.sock" \
exec ~/myapp/src/bin/www
Replace ~/myapp/src/bin/www
with the path to your app.
Ruby
#!/bin/bash -e
eval "$(rbenv init -)"
cd ~/myapp/src
RAILS_ENV=production \
exec bin/rails server -b /home/crsid/myapp/web.sock
Replace ~/myapp/src
with the path to your app.
Now, for all frameworks, make the run.sh
script executable:
chmod +x ~/myapp/run
You should now be able to execute the script and access your website (or see any errors in your terminal).
Using systemd
The SRCF may restart any of its servers as part of regular or emergency
maintenance. When this happens, you’ll likely want your app to start up
again. Similarly you may want your app to automatically restart if it
crashes. We highly recommend using systemd
to supervise your app.
-
Create a directory for your app
~/myapp
. -
Place a startup script at
~/myapp/run.sh
. Your script should end byexec
ing the server process. If you followed one of our tutorials for Django, Node or Rails, you’ve already created this file, so can move on to the next step. Otherwise, an example would be:#!/bin/sh -e exec ~/myapp/run-server
Your server should run in the foreground (it should not daemonise), and the
run.sh
script should end with anexec
line so that signals are sent to the server (and not to the shell that started it).Once you’ve written the script, make it executable (
chmod +x ~/myapp/run.sh
). Test it by executing it in your terminal before moving on; it will be easier to debug problems. -
Write a systemd service file so your app will be supervised on startup.
For applications in your personal account, create the unit directory if it doesn’t exist:
mkdir -p ~/.config/systemd/user
For a group account, substitute
~
for/societies/foosoc
, wherefoosoc
is the short name of the account.Then, save the following to the file
~/.config/systemd/user/myapp.service
(or/societies/foosoc/.config/systemd/user/myapp.service
for groups):[Unit] Description={YOUR USER, SOCIETY OR GROUP NAME} Webapp ConditionHost=sinkhole [Install] WantedBy=default.target [Service] ExecStart=/home/{CRSid}/myapp/run Restart=always
-
Tell systemd to start your app on startup, by running
systemctl --user enable myapp
. -
You’ll need to start your app manually once (on future reboots, it will be started for you). To do that, run
systemctl --user start myapp
.To control your app, you can use the
systemctl
tool. Seeman systemctl
for full details.In summary,
- Restart an app.
systemctl --user restart myapp
- Bring an app offline.
systemctl --user stop myapp
- Bring an app back online.
systemctl --user start myapp
- Check the status of an app.
systemctl --user status myapp
By default, your app’s standard output and error streams are sent to systemd’s journal however only the root user can access these. You will want to make your app write to a log file rather than stdout or stderr.
- Restart an app.
We have further more general reading on systemd
services.
Notes for Python
Installing gunicorn
We recommend using gunicorn to serve your application. After activating
your venv
, install it with pip install gunicorn
.
Note that you may see a warning about a syntax error. As long as the output ends in ‘Successfully installed gunicorn’, it’s safe to ignore this.
Reloading your app
Gunicorn will reload your app if you send it SIGHUP. You can teach
systemd that fact by adding the following line under [Service]
in your
systemd unit file:
ExecReload=/bin/kill -HUP $MAINPID
and then running systemctl --user daemon-reload
. After that, you can
use systemctl
to reload your app:
systemctl --user reload myapp
Notes for Ruby
- You may need to migrate your database first.
- Make sure you’ve set secret keys for the app and any gems that need them (e.g. Devise).
- Static file serving is off by default in production, but you’ll
want to turn it on: set both
config.assets.compile
andconfig.public_file_server.enabled
to true inconfig/environments/production.rb
.
Forwarding requests
You then need to configure Apache to forward web requests to the
web.sock
socket specified in the run.sh
script. We explain how to do
this in the reference for web apps.
Closing remarks
Did you like this or find this cool? We invite you to check out more tutorials or get in touch to tell us what you thought!
If you have any suggestions for how we could improve this documentation
please send us an email at support@srcf.net
or submit a Pull Request
on GitHub!