What is the best folder structure for a django project so as to make these things easy:
- Testing
- Separating prod/dev settings
- managing templates
- easy deployment
- Easy handle of static/media files
- Any other best practice
What is the best folder structure for a django project so as to make these things easy:
I recommend using django-cookiecutter it generates a well-formatted project structure out of the box.
mysite-polls-templates-polls-base.html---------the best folder structure. you can also delete the folder of second polls. but the first structure is recommended.
Well, first I’m going to make a Dockerfile based on alpine with uWSGI to handle static files. I prefer to not have configuration files, so I tend to have long command lines, but you could do otherwise if you wanted, also note that djcli dbcheck
waits for the DB to be ready for Django, so it will look something like this:
CMD /usr/bin/dumb-init bash -euxc "djcli dbcheck && ./manage.py migrate --noinput \
&& uwsgi \
--spooler=/spooler \ # required for djcall
--http-socket=0.0.0.0:8000 \
--chdir=/app \
--plugin=python,http,router_static,router_cache \
--module=$UWSGI_MODULE \
--http-keepalive \
--harakiri=120 \
--max-requests=100 \
--master \
--workers=24 \
--processes=12 \
--chmod=666 \
--log-5xx \
--vacuum \
--enable-threads \
--post-buffering=8192 \
--ignore-sigpipe \
--ignore-write-errors \
--disable-write-exception \
--mime-file /etc/mime.types \
--offload-threads '%k' \
--file-serve-mode x-accel-redirect \
--route '^/static/.* addheader:Cache-Control: public, max-age=7776000' \
--static-map $STATIC_ROOT=$STATIC_URL \
--static-gzip-all \
--cache2 'name=statcalls,items=100' \
--static-cache-paths 86400 \
--static-cache-paths-name statcalls"
But that means that CI should build the image, example with gitlab-ci, relying on environment variables to push on the registry of the repo by default:
build:
image: docker:dind
stage: build
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
Then, some docker-compose to add other services and not be limited to deploying just a django container:
docker-compose.yml
: your base servicesdocker-compose.override.yml
: overrides for localhost developmentOf course, I don’t have rabbitmq nor memcached in them because I use uWSGI which provides such features natively. But there is something I need to make sure, is that the Django/uWSGI docker-compose service contains such labels, so that Traefik will be able to route them, and a variable image so that I can change it:
image: ${IMAGE}
labels:
- "traefik.enable=true"
- "traefik.frontend.rule=Host: ${HOST}"
- "traefik.port=8000"
The first thing I’m going to do on a new server is a couple of commands:
bigsudo yourlabs.ssh @somehost
install my keys, harden configuration …bigsudo yourlabs.traefik @somehost
install docker and traefikbigsudo yourlabs.ssh adduser username=someuser @somehost
Then, I can deploy the project with one command:
export HOST=staging.somehost
export IMAGE=$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
docker-compose up
Note that this will work if your CI server hosts staging.somehost, it’s extremely fast when you run it on the server that built the image because then docker will not have to pull the image from anywhere: great for ephemeral (per-branch) deployments, that I will show here because for me it’s absolutely required to maintain a clean master branch, that a developer does not need to push un-finished stuff to master to show ongoing development to the product team, ie. with this config:
review-deploy:
image: yourlabs/docker
stage: test
artifacts:
paths:
- target
environment:
name: test/$CI_COMMIT_REF_NAME
url: http://${CI_ENVIRONMENT_SLUG}.ci.somehost
on_stop: review-stop # thanks to this, gitlab will auto-destroy deployment on merge
script:
- export HOST=${CI_ENVIRONMENT_SLUG}.ci.somehost
- export IMAGE=$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
- docker-compose --project-name $CI_ENVIRONMENT_SLUG up --detach --no-build
after_script:
- docker-compose --project-name $CI_ENVIRONMENT_SLUG logs
- docker-compose --project-name $CI_ENVIRONMENT_SLUG ps
except:
refs:
- master
- staging
- production
Anyway, adding more is also fine, you can cumulate them with -f
docker-compose flag:
docker-compose.ephemeral.yml
: does not rely on any filesystem volume for per-branch temporary deployments,docker-compose.persistent.yml
, relies on having a home directory on the filesystem to persist data into, for staging/productionBut then CI needs to deploy on production, which should be on another server, in this case I use bigsudo again:
production:
image: yourlabs/ansible
stage: production
environment:
name: production
url: https://prod.somehost
before_script:
- mkdir -p ~/.ssh; echo "$PROD_SSH_KEY" > ~/.ssh/id_ed25519; echo "$SSH_FINGERPRINTS" > ~/.ssh/known_hosts; chmod 700 ~/.ssh; chmod 600 ~/.ssh/*
script:
- export $(echo $PROD_EMAIL_SETTINGS | xargs)
- export $(echo $PROD_ENV | xargs)
- HOST=prod.somehost
bigsudo yourlabs.compose
compose=docker-compose.yml,docker-compose.production.yml
compose_backend_image=$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
compose_backend_build=
docker_auth_username=$CI_REGISTRY_USER
docker_auth_password=$CI_REGISTRY_PASSWORD
docker_auth_registry=$CI_REGISTRY
home=/home/production
deploy@prod.somehost
-v | tee out
- grep unreachable=0 out
- grep failed=0 out
This is really a small extract of the simple but sophisticated setup I like.