London, United Kingdom

(+44) 07788.377.460 [email protected]

A successful directory structure

Don’t just dump it all in the root! If software development is your job, then just don’t be that guy!

Consider a project like the one described in my Advanced WordPress and Docker or FastAPI on Docker posts. I prefer to organise my code into directories for the application code (PHP, Python, WordPress, etc.), infrastructure as code (any dockerfiles), database (SQL imports / scripts), any webservers (e.g. Nginx, Apache, etc.); then in the root I would only have my ENV file, a couple of shell scripts, and maybe some ignore files (.gitignore, .dockerignore, etc.). Not even a docker-compose.yml in there!

I’ve been so far unsuccessful in advocating for the following directory structure (which I find very clear and modular, and aesthetically appealing to me):

root
  |--- app
      |--- (the application code)
  |--- database
      |--- test-data.sql
  |--- docker
      |--- docker-compose-live.yml
      |--- docker-compose-local.yml
      |--- dockerfile.live
      |--- dockerfile.local
--- .dockerignore
--- .gitignore
--- build.sh
--- README.md

Then my build.sh shell script would need to do all the heavy lifting for me by installing the dependencies in a virtual environment, pull the latest Docker images, run the containers, execute automated tests, as well as push new images to my Docker Hub:

build.sh
#!/bin/bash

python -m venv .venv && \
  source .venv/bin/activate && \
  .venv/bin/pip3 install --no-cache-dir --upgrade pip && \
  .venv/bin/pip3 install --no-cache-dir --upgrade -r requirements.txt

ImageName='micro-service'
TagName='local'

read -p 'Confirm ENV [local, staging, prod]: ' confirmEnv
case $confirmEnv in
  [staging]* ) TagName='staging';;
  [prod]* ) TagName='prod';;
  *) TagName='local';;
esac


black *.py


if python3 app/tests/*.py; then
    echo "Tests passed! Continuing with tag:${TagName} build."
else
    echo "TESTS FAILED! Are you sure you want to continue to build tag:${TagName}?"
    read -p "Confirm [y/N]: " confirmYN
    case $confirmYN in
      [Yy]* ) ;;
      *) exit;;
    esac
fi


echo "Building ${ImageName} image with tag ${TagName}.";
echo "Press CTRL+C within 2 seconds to stop building."
sleep 3;


DockerFile = "dockerfile.${TagName}"
DockerComposeFile = "docker-compose-${TagName}.yml"

## These docker files (which I'm copying to the root) 
## should be Git ignored, or deleted at the end of 
## the execution of this script.

cp docker/${DockerFile} ./DockerFile
cp docker/${DockerComposeFile} ./docker-compose.yml
docker build --no-cache -t ${DockerImage} . -f ${DockerFile}

read -p "Push image to Docker Hub? [y/N]: " confirmYN
case $confirmYN in
  [Yy]* ) docker push ${DockerImage};;
  *) continue;;
esac

docker compose up -d [--build] [--force-recreate] [--remove-orphans]

# rm ${DockerFile}

For a WordPress website for instance my repository will be structured like so:

root
  |--- database
      |--- test-data.sql
  |--- docker
      |--- docker-compose-live.yml
      |--- docker-compose-local.yml
      |--- live
          |--- app.dockerfile
          |--- db.dockerfile
          |--- proxy.dockerfile
      |--- local
          |--- app.dockerfile
          |--- db.dockerfile
          |--- proxy.dockerfile
  |--- proxy
      |--- nginx.conf
      |--- ssl
         |--- fullchain.pem
         |--- privkey.pem
  |--- php
      |--- custom.ini
  |--- wordpress
      |--- wp-plugins/...
      |--- wp-uploads/...
      |--- wp-themes/...
--- .dockerignore
--- .gitignore
--- build.sh
--- README.md

Any thoughts? Wait… I don’t take comments on the website. So should you do want to reach out, please do so via Twitter.