nickjj / docker-django-example

A production ready example Django app that's using Docker and Docker Compose.
MIT License
1.22k stars 265 forks source link

Double public and public_collected directories #19

Closed philgyford closed 2 years ago

philgyford commented 2 years ago

After building this, and running ./run manage collectstatic I've noticed there are four directories for static assets in the web container (in addition to /app/assets), but only two are used:

I wonder if this means there are some unnecessary creations in the Dockerfile, but I'm not sure which you intended to be correct? Or have I misunderstood something?

nickjj commented 2 years ago

Hi,

The (2) /public*/ directories are there to hold your assets in a non-volume mounted location so they never get overwritten by your volumes. These get populated at build time when you build your Docker image, the /public_collected/ directory gets populated when collectstatic gets run based on having set DEBUG=false in your .env at build time.

At runtime (thanks to https://github.com/nickjj/docker-django-example/blob/main/bin/docker-entrypoint-web) the public_collected/ directory gets copied over to /app/public_collected where your volume can take over to allow you to persist files back to your Docker host and potentially serve these assets from a web server not running in Docker such as nginx.

It is pretty convoluted but I don't see a way around all of this. We have to meet 2 criteria here:

If you have a way to meet both of these criteria in a cleaner way I'm all for improving it, keep in mind it would need to work in development too where assets aren't collected. I'd love to be able to get rid of this workflow.

philgyford commented 2 years ago

Thanks that makes sense. I've only been using this for local development so far, so haven't quite grasped the differences with how it would work in production.

Previously I've only used manage.py runserver during development, not gunicorn, and added "whitenoise.runserver_nostatic" to INSTALLED_APPS if DEBUG=True. Which I think means I don't need to run collectstatic except on the production server (where I wasn't using Docker).

I assume we need to run collectstatic in development because we're using gunicorn?

Now that I'm thinking of using Docker for both dev and production I have this extra level of complication to get my head round I guess!

In my dabblings with Docker and Django I'm always frustrated that dealing with static files ends up being so complicated!

nickjj commented 2 years ago

You don't need to run collectstatic in development.

The exact instructions in the readme file combined with the source code in this repo will work out of the box and your assets will get served in development.

The collection process is mainly around digesting the files with an md5 hash, it's aimed at production although there's nothing stopping you from digesting them manually in development. It happens in production by default due to the Dockerfile having a condition to run it when DEBUG=false:

https://github.com/nickjj/docker-django-example/blob/b87f4654da1c4c5b7d1d81fffc42445c51cea9e6/Dockerfile#L64-L66

philgyford commented 2 years ago

Sorry, I'd just now realised collectstatic is not run in development and was coming here to correct myself. Apologies; it's hard to follow exactly what's going on.

nullbio commented 6 months ago

Found this issue because I was wondering the same @nickjj

We need to build both sets of public files in a non-volume mounted directory but also make them available in a volume at runtime in such a way where existing files in the volume on the Docker host at runtime won't overwrite the newly built public files, this means we need to store both sets of public/ directories in 2 locations

Why is this the case? Why not just build static assets into /app/public (binded to assets/public on the host) for development, and serve from this folder in development? And into /app/public_collected for production?

I don't understand the overwriting files issue, can you please explain a little more? Are you saying because you don't want your production build assets to overwrite the dev build assets? If so, why does it matter? I would imagine that's only an issue if trying to test your production flow locally, but in that scenario does it really matter if we overwrite the files when they can just be recompiled/generated again? And in the case where the prod build is happening on a different machine for deployments you won't have dev files there to overwrite anyway. Let me know if I'm missing something. Thanks.

nickjj commented 6 months ago

@nullbio You quoted one of my bullets but the first bullet is:

Django does not let you output collectstatic in the same directory where your source is, this means we need 2 sets of public/ directories, I named them accordingly

We can't collectstatic in public/ because Django prevents this. Now, that was 2 years ago and I'm not sure if anything changed since then but back then if assets got built into public/ then collectstatic couldn't output its files in public/, instead you had to use a separate directory and that's why I chose public_collected/.