This is a web app showing latest data from mik4el's gadgets using Django (2.0.12), Angular 8 and Docker. The app is currently available at https://m4bd.se.
The web app is separated into a backend web/gadget_board_backend
using Django and a frontend web/gadget_board_frontend
using Angular.
The full stack is running in Docker containers and consists of:
- "nginx_dev" or "nginx_prod": nginx for web server and serving static files.
- "web": Django served by gunicorn.
- "postgres": the postgres database.
- Persisting data volumes "postgres_data_dev" or "postgres_data" for the database.
This is also my way to learn what can be considered to be efficient development and deployment workflows in 2016. My goal's for the workflow is:
- Closely mimick development environment and production environment
- Quick feedback loop from code change to feature in development
- Fast deployment to production of the entire stack as well as small updates
- Entire stack is in repo
- Easy to add services
- Deployment agnostic for hosting providers
Suggested reading and inspiration for this repo: https://realpython.com/blog/python/django-development-with-docker-compose-and-machine/ (Good concept and introduction but some code is out-of-date and following instructions will not give a working setup, see blog comments for more info)
- Docker for mac or similar.
- Node v10+.
- Ng-cli installed globally.
- VirtualBox
This will start a new development environment and serve the web app on your machine. This requires the download of all depedencies which will take some time.
docker-machine create -d virtualbox dev
eval $(docker-machine env dev)
- Make your own
.env
-file with credentials e.g.cp .env_template .env
docker-compose build
docker-compose up -d
docker-compose run --rm web python manage.py migrate
docker-compose run --rm web python manage.py createsuperuser
cd web/gadget_board_frontend
ng build
- Open a browser at the ip from
docker-machine ip dev
For normal development work, I suggest this workflow:
- Make change in Django related code, run
manage.py
commands usingdocker-compose run --rm web python manage.py startapp
etc... - Reload browser, run tests etc.
Special cases:
- Make sure no old code is running if you are changing e.g.
settings.py
orurls.py
by restarting the web container. Do this withdocker-compose restart web
. - Run
manage.py
commands usingdocker-compose run --rm
, e.g.docker-compose run --rm web python manage.py makemigrations
. - If you restart your computer etc, you may need to restart the machine running the containers, do so by:
docker-machine start dev
eval $(docker-machine env dev)
(also when you open a new terminal)
When you add a dependency to web/requirements.txt
you need to build a new container image and restart the container with this new image, this is done by:
docker-compose build web
docker-compose up -d
- Possibly run
docker-compose run --rm web python manage.py collectstatic
or other commands your dependency requires. NB: collectstatic needs to be run in the development workflow for static files to be saved in the static-dir, this is not possible to do on deployed containers.
For normal development work, I suggest this workflow:
ng build --watch
- Make change in source file in
gadget_board_frontend
- Reload browser
See ng-cli documentation: https://github.com/angular/angular-cli#3rd-party-library-installation
Now we need to set up the production environment to which you are deploying. By using Docker the production environment is very agnostic to what provider you choose. I like DigitalOcean for small projects that can grow but there are many options. Doing the first deployment requires you to migrate the database for the first time, also we use a different docker-compose file so you need to rebuild the container images.
- Get working SSL certs e.g. by following the steps below.
- Get an access token for your DigitalOcean account.
docker-machine create -d digitalocean --digitalocean-access-token=<token> --digitalocean-region=fra1 production
(use same region where your floating ip is for the domain you use in SSL)eval $(docker-machine env production)
ng build --prod --output-path=../gadget_board_frontend_dist
docker-compose -f production.yml build
docker-compose -f production.yml up -d
docker-compose -f production.yml run --rm web python manage.py migrate
docker-compose -f production.yml run --rm web python manage.py createsuperuser
Since the app handles user data securing traffic using SSL is a requirement for production. To set it up on the production nginx container I followed these steps:
- Have a domain ready that you control. It is nice to point the domain towards a floating ip from e.g. DigitalOcean so you can change the production environment without needing to update your dns.
- Find a suitable SSL certificate authority (CA), I use positivessl from namecheap.com. For SSL certs with short expiration dates there are also free options but that is a hassle so I value buying an SSL cert. A better free option is https://letsencrypt.org/.
- Make a csr-file by running
openssl req -newkey rsa:2048 -nodes -keyout example.com.key -out example.com.csr
and go through the process of obtaining the cert. Save the key and csr file safely on your local machine. - When you have got all the cert files back from your CA, prepare the cert for nginx by following e.g.
https://www.namecheap.com/support/knowledgebase/article.aspx/9419/0/nginx
. Store the files safely on your local machine. - Download root certificate from your CA, e.g.
https://www.namecheap.com/support/knowledgebase/article.aspx/9393/69/where-do-i-find-ssl-ca-bundle
, save it asPositiveSSLBundle.cer
- Make a dhparam.pem file by running
openssl dhparam 2048
and store safely. - Copy over the files to a new directory
ssl
in foldernginx
. They will not be added to git.
When you are deploying the next time we also need to rebuild the container that has changed files since the production environment does not mount your local machines files.
ng build --prod --output-path=../gadget_board_frontend_dist
docker-compose -f production.yml build
docker-compose -f production.yml down
docker-compose -f production.yml up -d
- Or run the docker-compose commands together:
docker-compose -f production.yml build && docker-compose -f production.yml down && docker-compose -f production.yml up -d
NB: If in a new terminal remembereval $(docker-machine env production)
.
Testing for the backend is handled by the default Django test system, so running tests is easy, e.g:
docker-compose run --rm web python manage.py test
Testing for the frontend is done using angular-cli:
ng test
A deployed environment can be backed up by your hosting provider, e.g. DigitalOcean. Since this is a very stateless deployment you can also make a scripted backup of your database and make it possible to easily restore the database from a backup. This will save some on hosting costs and make for a more self-contained and hosting provider agnostic solution.
Example command to run backup of postgres db on development machine:
docker-compose run --rm -e PGPASSWORD=postgres postgres pg_dump -U postgres -p 5432 -h postgres postgres > postgres_db_20160913_development.bak
To restore development machine:
docker-compose up -d
docker-compose run --rm -e PGPASSWORD=postgres postgres psql -U postgres -p 5432 -h postgres -F c < postgres_db_20160913_development.bak
Example command to create backup of postgres db on production machine:
docker-compose -f production.yml run --rm -e PGPASSWORD=postgres postgres pg_dump -U postgres -p 5432 -h postgres postgres > postgres_db_20190606_production.bak
To restore production machine:
docker-compose -f production.yml up -d
docker-compose -f production.yml run --rm -e PGPASSWORD=postgres postgres psql -U postgres -p 5432 -h postgres -F c < postgres_db_20180522_production.bak
The system has produced a lot of Gadget Data, in a year about 11M Gadget Data objects were stored. This becomes many GB easily so to make sure the system can run efficiently on a small VM it makes sense to purge Gadget Data from time to time. An easy and safe way to purge is to use the Django ORM, this is an example:
docker-compose -f production.yml run --rm web python manage.py shell
>>> from gadgets.models import GadgetData
>>> GadgetData.objects.all().delete()
- Frontend update css to not look broken
- Frontend: Animation when data updated
- Refresh JWT tokens in background
- Sass or similar css build
- Tests for account service
- BUG: Redirect after new account didn’t work
- Create new password functionality
- Fullscreen logic in routes rather than in swimthermocomponent
- More info link for gadget detail
- Next bus gadget data
- Next bus gadget visualization
- Show historic data for gadgets
- Django Channels for e.g. Flight radar
- Fancy map visualization for Flight radar
- My own component class that animates, has standard css and so on.