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.