├── .dockerignore ├── .editorconfig ├── .env.example ├── .github ├── FUNDING.yml ├── docs │ └── screenshot.jpg └── workflows │ └── ci.yml ├── .gitignore ├── .hadolint.yaml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── assets ├── .yarnrc ├── css │ └── app.css ├── esbuild.config.mjs ├── js │ └── app.js ├── package.json ├── static │ ├── 502.html │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── images │ │ └── django.png │ ├── maintenance.html │ ├── mstile-150x150.png │ ├── robots.txt │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── tailwind.config.js └── yarn.lock ├── bin ├── docker-entrypoint-web ├── rename-project └── uv-install ├── compose.yaml ├── public └── .keep ├── public_collected └── .keep ├── pyproject.toml ├── run ├── src ├── __init__.py ├── config │ ├── __init__.py │ ├── asgi.py │ ├── celery.py │ ├── gunicorn.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── pages │ ├── __init__.py │ ├── apps.py │ ├── templates │ │ └── pages │ │ │ └── home.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── templates │ └── layouts │ │ └── index.html └── up │ ├── __init__.py │ ├── apps.py │ ├── tests.py │ ├── urls.py │ └── views.py └── uv.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .ruff_cache 3 | .pytest_cache/ 4 | __pycache__/ 5 | assets/node_modules/ 6 | public/ 7 | public_collected/ 8 | 9 | .coverage 10 | .dockerignore 11 | .env* 12 | !.env.example 13 | celerybeat-schedule 14 | docker-compose.override.yml 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [[sh]] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | [[bash]] 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Default values are optimized for production to avoid having to configure 2 | # much in production. 3 | # 4 | # However it should be easy to get going in development too. If you see an 5 | # uncommented option that means it's either mandatory to set or it's being 6 | # overwritten in development to make your life easier. 7 | 8 | # Enable BuildKit by default: 9 | # https://docs.docker.com/develop/develop-images/build_enhancements 10 | export DOCKER_BUILDKIT=1 11 | 12 | # Rather than use the directory name, let's control the name of the project. 13 | export COMPOSE_PROJECT_NAME=hellodjango 14 | 15 | # In development we want all services to start but in production you don't 16 | # need the asset watchers to run since assets get built into the image. 17 | # 18 | # You can even choose not to run postgres and redis in prod if you plan to use 19 | # managed cloud services. Everything "just works", even optional depends_on! 20 | #export COMPOSE_PROFILES=postgres,redis,web,worker 21 | export COMPOSE_PROFILES=postgres,redis,assets,web,worker 22 | 23 | # If you're running native Linux and your uid:gid isn't 1000:1000 you can set 24 | # these to match your values before you build your image. You can check what 25 | # your uid:gid is by running `id` from your terminal. 26 | #export UID=1000 27 | #export GID=1000 28 | 29 | # In development avoid writing out bytecode to __pycache__ directories. 30 | #PYTHONDONTWRITEBYTECODE= 31 | export PYTHONDONTWRITEBYTECODE=true 32 | 33 | # You should generate a random string of 50+ characters for this value in prod. 34 | # You can generate a secure secret by running: ./run secret 35 | export SECRET_KEY=insecure_key_for_dev 36 | 37 | # This should never be set to true in production but it should be enabled in dev. 38 | #export DEBUG=false 39 | export DEBUG=true 40 | 41 | # Which Node environment is running? This should be "development" or "production". 42 | #export NODE_ENV=production 43 | export NODE_ENV=development 44 | 45 | # A comma separated list of allowed hosts. In production this should be your 46 | # domain name, such as "example.com,www.example.com" or ".example.com" to 47 | # support both example.com and all sub-domains for your domain. 48 | # 49 | # This is being overwritten in development to support multiple Docker dev 50 | # environments where you might be connecting over a local network IP address 51 | # instead of localhost. You should not use "*" in production. 52 | #export ALLOWED_HOSTS=".localhost,127.0.0.1,[::1]" 53 | export ALLOWED_HOSTS="*" 54 | 55 | # The bind port for gunicorn. 56 | # 57 | # Be warned that if you change this value you'll need to change 8000 in both 58 | # your Dockerfile and in a few spots in compose.yaml due to the nature of 59 | # how this value can be set (Docker Compose doesn't support nested ENV vars). 60 | #export PORT=8000 61 | 62 | # How many workers and threads should your app use? WEB_CONCURRENCY defaults 63 | # to the server's CPU count * 2. That is a good starting point. 64 | #export WEB_CONCURRENCY= 65 | #export PYTHON_MAX_THREADS=1 66 | 67 | # Do you want code reloading to work with the gunicorn app server? 68 | #export WEB_RELOAD=false 69 | export WEB_RELOAD=true 70 | 71 | # Configure the timeout value in seconds for gunicorn. 72 | #export WEB_TIMEOUT=120 73 | 74 | # You'll always want to set POSTGRES_USER and POSTGRES_PASSWORD since the 75 | # postgres Docker image uses them for its default database user and password. 76 | export POSTGRES_USER=hello 77 | export POSTGRES_PASSWORD=password 78 | #export POSTGRES_DB=hello 79 | #export POSTGRES_HOST=postgres 80 | #export POSTGRES_PORT=5432 81 | 82 | # Connection string to Redis. This will be used for the cache back-end and for 83 | # Celery. You can always split up your Redis servers later if needed. 84 | #export REDIS_URL=redis://redis:6379/0 85 | 86 | # You can choose between DEBUG, INFO, WARNING, ERROR, CRITICAL or FATAL. 87 | # DEBUG tends to get noisy but it could be useful for troubleshooting. 88 | #export CELERY_LOG_LEVEL=info 89 | 90 | # Should Docker restart your containers if they go down in unexpected ways? 91 | #export DOCKER_RESTART_POLICY=unless-stopped 92 | export DOCKER_RESTART_POLICY=no 93 | 94 | # What health check test command do you want to run? In development, having it 95 | # curl your web server will result in a lot of log spam, so setting it to 96 | # /bin/true is an easy way to make the health check do basically nothing. 97 | #export DOCKER_WEB_HEALTHCHECK_TEST=curl localhost:8000/up 98 | export DOCKER_WEB_HEALTHCHECK_TEST=/bin/true 99 | 100 | # What ip:port should be published back to the Docker host for the app server? 101 | # 102 | # If you have a port conflict because something else is using 8000 then you 103 | # can either stop that process or change 8000 to be something else. 104 | # 105 | # Use the default in production to avoid having gunicorn directly accessible on 106 | # the internet since it'll very likely be behind nginx or a load balancer. 107 | # 108 | # This is being overwritten in dev to be compatible with more dev environments, 109 | # such as accessing your site on another local device (phone, tablet, etc.). 110 | export DOCKER_WEB_PORT_FORWARD=8000 111 | 112 | # What volume path should be used? In dev we want to volume mount everything 113 | # so that we can develop our code without rebuilding our Docker images. 114 | #export DOCKER_WEB_VOLUME=./public_collected:/app/public_collected 115 | export DOCKER_WEB_VOLUME=.:/app 116 | 117 | # What CPU and memory constraints will be added to your services? When left at 118 | # 0, they will happily use as much as needed. 119 | #export DOCKER_POSTGRES_CPUS=0 120 | #export DOCKER_POSTGRES_MEMORY=0 121 | #export DOCKER_REDIS_CPUS=0 122 | #export DOCKER_REDIS_MEMORY=0 123 | #export DOCKER_WEB_CPUS=0 124 | #export DOCKER_WEB_MEMORY=0 125 | #export DOCKER_WORKER_CPUS=0 126 | #export DOCKER_WORKER_MEMORY=0 127 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | github: "nickjj" 4 | custom: ["https://www.paypal.me/nickjanetakis"] 5 | -------------------------------------------------------------------------------- /.github/docs/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/.github/docs/screenshot.jpg -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "*" 7 | push: 8 | branches: 9 | - "main" 10 | - "master" 11 | schedule: 12 | - cron: "30 12 * * *" 13 | 14 | jobs: 15 | test: 16 | runs-on: "ubuntu-22.04" 17 | 18 | steps: 19 | - uses: "actions/checkout@v4" 20 | 21 | - name: "Install CI dependencies" 22 | run: | 23 | ./run ci:install-deps 24 | 25 | - name: "Test" 26 | run: | 27 | # Remove volumes in CI to avoid permission errors due to UID / GID. 28 | sed -i "s|.:/app|/tmp:/tmp|g" .env* 29 | sed -i "s|.:/app|/tmp:/tmp|g" compose.yaml 30 | 31 | # Django requires static files to be collected in order to run its 32 | # test suite. That means we need to generate production assets from 33 | # esbuild. This line ensures NODE_ENV is set to production. 34 | sed -i "s|export NODE_ENV|#export NODE_ENV|g" .env* 35 | 36 | ./run ci:test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mostly created by https://www.gitignore.io 2 | 3 | 4 | ### App ####################################################################### 5 | 6 | public/* 7 | !public/.keep 8 | public_collected/* 9 | !public_collected/.keep 10 | 11 | .env* 12 | !.env.example 13 | docker-compose.override.yml 14 | 15 | 16 | ### Python #################################################################### 17 | 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | pip-wheel-metadata/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | pytestdebug.log 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Flask stuff 65 | instance/ 66 | .webassets-cache 67 | 68 | # Celery stuff 69 | celerybeat-schedule 70 | celerybeat.pid 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | doc/_build/ 75 | 76 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 77 | __pypackages__/ 78 | 79 | # mkdocs documentation 80 | /site 81 | 82 | # mypy 83 | .mypy_cache/ 84 | .dmypy.json 85 | dmypy.json 86 | 87 | # Pyre type checker 88 | .pyre/ 89 | 90 | # pytype static type analyzer 91 | .pytype/ 92 | 93 | # profiling data 94 | .prof 95 | 96 | 97 | ### Node ###################################################################### 98 | 99 | # Dependency directories 100 | assets/node_modules/ 101 | 102 | # Optional eslint cache 103 | .eslintcache 104 | 105 | 106 | ### OSX ####################################################################### 107 | 108 | # General 109 | .DS_Store 110 | .AppleDouble 111 | .LSOverride 112 | 113 | # Icon must end with two \r 114 | Icon 115 | 116 | 117 | # Thumbnails 118 | ._* 119 | 120 | # Files that might appear in the root of a volume 121 | .DocumentRevisions-V100 122 | .fseventsd 123 | .Spotlight-V100 124 | .TemporaryItems 125 | .Trashes 126 | .VolumeIcon.icns 127 | .com.apple.timemachine.donotpresent 128 | 129 | # Directories potentially created on remote AFP share 130 | .AppleDB 131 | .AppleDesktop 132 | Network Trash Folder 133 | Temporary Items 134 | .apdisk 135 | 136 | 137 | ### Vim ####################################################################### 138 | 139 | # Swap 140 | [._]*.s[a-v][a-z] 141 | !*.svg # comment out if you don't need vector files 142 | [._]*.sw[a-p] 143 | [._]s[a-rt-v][a-z] 144 | [._]ss[a-gi-z] 145 | [._]sw[a-p] 146 | 147 | # Session 148 | Session.vim 149 | Sessionx.vim 150 | 151 | # Temporary 152 | .netrwhist 153 | # Auto-generated tag files 154 | tags 155 | # Persistent undo 156 | [._]*.un~ 157 | 158 | 159 | ### VSCode #################################################################### 160 | 161 | .vscode/* 162 | !.vscode/settings.json 163 | !.vscode/tasks.json 164 | !.vscode/launch.json 165 | !.vscode/extensions.json 166 | *.code-workspace 167 | 168 | 169 | ### Emacs ##################################################################### 170 | 171 | # -*- mode: gitignore; -*- 172 | *~ 173 | \#*\# 174 | /.emacs.desktop 175 | /.emacs.desktop.lock 176 | *.elc 177 | auto-save-list 178 | tramp 179 | .\#* 180 | 181 | # Org-mode 182 | .org-id-locations 183 | *_archive 184 | 185 | # flymake-mode 186 | *_flymake.* 187 | 188 | # eshell files 189 | /eshell/history 190 | /eshell/lastdir 191 | 192 | # elpa packages 193 | /elpa/ 194 | 195 | # reftex files 196 | *.rel 197 | 198 | # AUCTeX auto folder 199 | /auto/ 200 | 201 | # cask packages 202 | .cask/ 203 | dist/ 204 | 205 | # Flycheck 206 | flycheck_*.el 207 | 208 | # server auth directory 209 | /server/ 210 | 211 | # projectiles files 212 | .projectile 213 | 214 | # directory configuration 215 | .dir-locals.el 216 | 217 | # network security 218 | /network-security.data 219 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | failure-threshold: "style" 3 | ignored: 4 | - "DL3008" 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a 6 | Changelog](https://keepachangelog.com/en/1.0.0/). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | - `setuptools` Python dependency since Docker recently removed this from the official Python image 13 | - `./run pip3 [...]` to run any Pip command 14 | - `./run yarn [...]` to run any Yarn command 15 | - `./run lint:shell` for linting shell scripts with ShellCheck 16 | - `./run format:shell` for formatting shell scripts with shfmt 17 | 18 | ### Changed 19 | 20 | - Replace `./run pip3:install` with `./run deps:install [--no-build]` to install any deps 21 | - Replace `./run yarn:install` with `./run deps:install [--no-build]` to install any deps 22 | - Allow overriding `$TTY` as an environment variable in the `run` script 23 | - Use `.hadolint.yaml` to configure Hadolint instead of inline flags 24 | - Replace Black, flake8 and isort with Ruff 25 | - Replace `pip3` with `uv` for Python package management (~10x speed boost!) 26 | - Refactor `Dockerfile` to use multi-stage app builds (~50% / 250MB image size reduction!) 27 | 28 | #### Languages and services 29 | 30 | - Update `Python` to `3.13.3` 31 | - Update `Node` to `22.16.0` 32 | - Update `Postgres` to `17.5` 33 | - Update `Redis` to `8.0.2` 34 | 35 | #### Back-end dependencies 36 | 37 | - Update `celery` to `5.5.3` 38 | - Update `django-debug-toolbar` to `5.2.0` 39 | - Update `django` to `5.2.1` 40 | - Update `gunicorn` to `23.0.0` 41 | - Update `psycopg` to `3.2.9` 42 | - Update `redis` to `6.2.0` 43 | - Update `ruff` to `0.11.12` 44 | - Update `setuptools` to `80.9.0` 45 | - Update `whitenoise` to `6.9.0` 46 | 47 | #### Front-end dependencies 48 | 49 | - Update `@tailwindcss/cli` to `4.1.8` 50 | - Update `@tailwindcss/postcss` to `4.1.8` 51 | - Update `esbuild` to `0.25.5` 52 | - Update `tailwindcss` to `4.1.8` 53 | 54 | ## [0.11.0] - 2024-08-09 55 | 56 | ### Added 57 | 58 | - `django-debug-toolbar` package 59 | - `WEB_TIMEOUT` environment variable to configure gunicorn's timeout value (defaults to 120s) 60 | 61 | ### Changed 62 | 63 | - Convert `SECRET_KEY` into a required env var 64 | - Add `required: false` to `depends_on` in `docker-compose.yml` (requires Docker Compose v2.20.2+) 65 | - Adjust `WEB_CONCURRENCY` to default to N number of CPU cores instead of 1 in development 66 | - Rename `docker-compose.yml` to `compose.yaml` to stick to the official Docker Compose spec 67 | 68 | #### Languages and services 69 | 70 | - Update `Python` to `3.12.5` 71 | - Update `Node` to `20.6.1` 72 | - Update `Postgres` to `16.3` 73 | - Update `Redis` to `7.2.5` 74 | 75 | #### Back-end dependencies 76 | 77 | - Update `Django` to `5.1` 78 | - Update `black` to `24.8.0` 79 | - Update `celery` to `5.4.0` 80 | - Update `django-debug-toolbar` to `4.4.6` 81 | - Update `flake8` to `7.1.1` 82 | - Update `gunicorn` to `22.0.0` 83 | - Update `isort` to `5.13.2` 84 | - Update `psycopg` to `3.2.1` 85 | - Update `redis` to `5.0.8` 86 | - Update `whitenoise` to `6.7.0` 87 | 88 | #### Front-end dependencies 89 | 90 | - Update `autoprefixer` to `10.4.20` 91 | - Update `esbuild` to `0.23.0` 92 | - Update `postcss-import` to `16.1.0` 93 | - Update `postcss` to `8.4.41` 94 | - Update `tailwindcss` to `3.4.8` 95 | 96 | ## [0.10.0] - 2023-05-13 97 | 98 | ### Added 99 | 100 | - Ability to customize `UID` and `GID` if you're not using `1000:1000` (check the `.env.example` file) 101 | - Output `docker compose logs` in CI for easier debugging 102 | - `isort` to auto-sort Python imports and a new `./run isort` command 103 | - `./run quality` to run `lint`, `isort` and `format` in 1 command 104 | 105 | ### Changed 106 | 107 | - Reference `PORT` variable in the `docker-compose.yml` web service instead of hard coding `8000` 108 | - Adjust Hadolint to exit > 0 if any style warnings are present 109 | - Rename `esbuild.config.js` to `esbuild.config.mjs` and refactor config for esbuild 0.17+ 110 | 111 | #### Languages and services 112 | 113 | - Update `Python` to `3.11.3` 114 | - Update `Node` to `18.15.0` 115 | - Update `Postgres` to `15.3` 116 | - Update `Redis` to `7.0.11` 117 | 118 | #### Back-end dependencies 119 | 120 | - Replace `psycopg2` with `psycopg` (3.1.9) 121 | - Update `Django` to `4.2.1` 122 | - Update `flake8` to `6.0.0` 123 | - Update `isort` to `5.12.1` 124 | - Update `redis` to `4.5.5` 125 | - Update `whitenoise` to `6.4.0` 126 | 127 | #### Front-end dependencies 128 | 129 | - Update `autoprefixer` to `10.4.14` 130 | - Update `esbuild` to `0.17.19` 131 | - Update `postcss-import` to `15.1.0` 132 | - Update `postcss` to `8.4.23` 133 | - Update `tailwindcss` to `3.3.2` 134 | 135 | ### Removed 136 | 137 | - `set -o nounset` from `run` script since it's incompatible with Bash 3.2 (default on macOS) 138 | 139 | ### Fixed 140 | 141 | - Ensure Flake8, Black and isort all use 79 as the max line length 142 | - HTML templates not reloading in development by using `loaders` in `src/config/setting.py` 143 | 144 | ## [0.9.0] - 2022-09-09 145 | 146 | ### Added 147 | 148 | - `set -o nounset` to `run` script to exit if there's any undefined variables 149 | - Adjust `x-assets` to use a `stop_grace_period` of `0` for faster CTRL+c times in dev 150 | 151 | ### Changed 152 | 153 | - Switch Docker Compose `env_file` to `environment` for `postgres` to avoid needless recreates on `.env` changes 154 | - Replace override file with Docker Compose profiles for running specific services 155 | - Update Github Actions to use Ubuntu 22.04 156 | - Enable BuildKit by default in the `.env.example` file 157 | 158 | #### Languages and services 159 | 160 | - Update `Python` to `3.10.5` 161 | - Update `Node` to `16.15.1` 162 | - Update `PostgreSQL` to `14.5` 163 | - Update `Redis` to `7.0.4` 164 | 165 | #### Back-end dependencies 166 | 167 | - Update `Django` to `4.1.0` 168 | - Update `black` to `22.6.0` 169 | - Update `flake8` to `5.0.4` 170 | - Update `celery` to `5.2.7` 171 | - Update `redis` to `4.3.4` 172 | - Update `whitenoise` to `6.2.0` 173 | 174 | #### Front-end dependencies 175 | 176 | - Update `autoprefixer` to `10.4.8` 177 | - Update `esbuild` to `0.15.2` 178 | - Update `postcss` to `8.4.16` 179 | - Update `tailwindcss` to `3.1.8` 180 | 181 | ### Removed 182 | 183 | - Docker Compose `env_file` property for `redis` to avoid needless recreates on `.env` changes 184 | - Drop support for Docker Compose v1 (mainly to use profiles in an optimal way, it's worth it!) 185 | 186 | ## [0.8.0] - 2022-05-15 187 | 188 | ### Added 189 | 190 | - `yarn cache clean` after `yarn install` in `Dockerfile` (Hadolint warning) 191 | - `--no-cache-dir` flag to `pip3 install` command in `bin/pip3-install` (Hadolint warning) 192 | - [esbuild-copy-static-files](https://github.com/nickjj/esbuild-copy-static-files) plugin to drastically improve how static files are copied (check `assets/esbuild.config.js`) 193 | 194 | ### Changed 195 | 196 | - Update Bash shebang to use `#!/usr/bin/env bash` in `pip3-install` and `docker-entrypoint-web` 197 | - Refactor `/up/` endpoint into its own app and add `/up/databases` as a second URL 198 | 199 | #### Languages and services 200 | 201 | - Update `Python` to `3.10.4` 202 | - Update `Node` to `16.14.2` 203 | - Update `PostgreSQL` to `14.2` 204 | - Update `Redis` to `7.0.0` 205 | 206 | #### Back-end dependencies 207 | 208 | - Update `Django` to `4.0.4` 209 | - Update `black` to `22.3.0` 210 | - Update `celery` to `5.2.6` 211 | - Update `psycopg2` to `2.9.3` 212 | - Update `redis` to `4.3.1` 213 | - Update `whitenoise` to `6.1.0` 214 | 215 | #### Front-end dependencies 216 | 217 | - Update `autoprefixer` to `10.4.7` 218 | - Update `esbuild` to `0.14.39` 219 | - Update `postcss-import` to `14.1.0` 220 | - Update `postcss` to `8.4.13` 221 | - Update `tailwindcss` to `3.0.24` 222 | 223 | ### Fixed 224 | 225 | - `COPY --chown=node:node ../ ../` has been fixed to be `COPY --chown=node:node . ..` 226 | 227 | ## [0.7.0] - 2021-12-25 228 | 229 | ### Added 230 | 231 | - `/node_modules/.bin` to `$PATH` to easier access Yarn installed binaries 232 | - `yarn:build:js` and `yarn:build:css` run script commands 233 | 234 | ### Changed 235 | 236 | - Update `assets/tailwind.config.js` based on the new TailwindCSS v3 defaults 237 | - Replace all traces of Webpack with esbuild 238 | - Move JS and CSS from `assets/app` to `assets/js` and `assets/css` 239 | - Rename `webpack` Docker build stage to `assets` 240 | - Copy all files into the `assets` build stage to simplify things 241 | - Replace `cp -a` with `cp -r` in Docker entrypoint to make it easier to delete older assets 242 | - Rename `run hadolint` to `run lint:dockerfile` 243 | - Rename `run flake8` to `run lint` 244 | - Rename `run black` to `run format` 245 | - Rename `run bash` to `run shell` 246 | 247 | #### Languages and services 248 | 249 | - Update `Node` to `16.13.1` 250 | 251 | #### Front-end packages 252 | 253 | - Update `postcss` to `8.4.5` 254 | - Update `tailwindcss` to `3.0.7` 255 | 256 | ### Removed 257 | 258 | - Deleting old assets in the Docker entrypoint (it's best to handle this out of band in a cron job, etc.) 259 | 260 | ## [0.6.0] - 2021-12-07 261 | 262 | ### Added 263 | 264 | - Lint Dockerfile with 265 | - `redis` package to fulfill Django 4.x's Redis cache back-end requirements 266 | 267 | ### Changed 268 | 269 | - Update `assets/tailwind.config.js` based on the new TailwindCSS v3 defaults 270 | 271 | #### Languages and services 272 | 273 | - Update `Node` to `14.18.1` 274 | - Update `PostgreSQL` to `14.1` and switch to Debian Bullseye Slim 275 | - Update `Redis` to switch to Debian Bullseye Slim 276 | 277 | #### Back-end packages 278 | 279 | - Update `Django` to `4.0` 280 | - Update `celery` to `5.2.1` 281 | - Update `flake8` to `4.0.1` 282 | - Update `psycopg2` to `2.9.2` 283 | - Update `redis` to `4.0.2` 284 | 285 | #### Front-end packages 286 | 287 | - Update `@babel/core` to `7.16.0` 288 | - Update `@babel/preset-env` to `7.16.4` 289 | - Update `@babel/register` to `7.16.0` 290 | - Update `autoprefixer` to `10.4.0` 291 | - Update `babel-loader` to `8.2.3` 292 | - Update `copy-webpack-plugin` to `10.0.0` 293 | - Update `css-loader` to `6.5.1` 294 | - Update `css-minimizer-webpack-plugin` to `3.2.0` 295 | - Update `mini-css-extract-plugin` to `2.4.5` 296 | - Update `postcss-loader` to `6.2.1` 297 | - Update `postcss` to `8.4.3` 298 | - Update `tailwindcss` to `2.2.19` 299 | - Update `webpack-cli` to `4.9.1` 300 | - Update `webpack` to `5.64.4` 301 | 302 | ### Removed 303 | 304 | - `django-redis` package since Django 4.x supports using Redis as a cache back-end now 305 | 306 | ## [0.5.0] - 2021-10-10 307 | 308 | ### Changed 309 | 310 | #### Languages and services 311 | 312 | - Update `Python` to `3.10.0` and switch to Debian Bullseye Slim 313 | - Update `PostgreSQL` to `14.0` 314 | - Update `Redis` to `6.2.6` 315 | 316 | #### Back-end packages 317 | 318 | - Update `Django` to `3.2.8` 319 | - Update `celery` to `5.1.2` 320 | - Update `psycopg2` to `2.9.1` 321 | - Update `whitenoise` to `5.3.0` 322 | 323 | #### Front-end packages 324 | 325 | - Update `@babel/core` to `7.15.8` 326 | - Update `@babel/preset-env` to `7.15.8` 327 | - Update `@babel/register` to `7.15.3` 328 | - Update `autoprefixer` to `10.3.7` 329 | - Update `copy-webpack-plugin` to `9.0.1` 330 | - Update `css-loader` to `6.4.0` 331 | - Update `css-minimizer-webpack-plugin` to `3.1.1` 332 | - Update `mini-css-extract-plugin` to `2.4.2` 333 | - Update `postcss-loader` to `6.1.1` 334 | - Update `postcss` to `8.3.9` 335 | - Update `tailwindcss` to `2.2.16` 336 | - Update `webpack-cli` to `4.9.0` 337 | - Update `webpack` to `5.58.1` 338 | 339 | ## [0.4.0] - 2021-06-11 340 | 341 | ### Added 342 | 343 | - `bin/rename-project` script to assist with renaming the project 344 | - Use Black to format Python code 345 | - Set `PYTHONPATH="."` in the Dockerfile 346 | 347 | ### Changed 348 | 349 | - Rename `src/hello/` directory to `src/config/` to be more portable 350 | - Replace `APP_NAME` in `run` script with `POSTGRES_USER` for connecting to psql 351 | - Avoid using multi-line imports with commas or parenthesis 352 | - Update Python from `3.9.2` to `3.9.5` 353 | - Update PostgreSQL from `13.2` to `13.3` 354 | - Update Redis from `6.0.10` to `6.2.4` 355 | - Update Tailwind from `2.1.0` to `2.1.2` 356 | - Update Django from `3.2` to `3.2.4` 357 | - Update django-redis from `4.12.1` to `5.0.0` 358 | - Update Celery from `5.0.5` to `5.1.0` 359 | - Update flake8 from `3.9.0` to `3.9.2` 360 | - Update all Webpack related dependencies to their latest versions 361 | - Use the Docker Compose spec in `docker-compose.yml` (removes `version:` property) 362 | 363 | ### Fixed 364 | 365 | - Set an empty ENTRYPOINT for the worker to avoid race conditions when copying static files 366 | - Fix `run` script error for unbound variable in older versions of Bash on macOS 367 | - Potential issue on Mac M1s by adding `depends_on` to Webpack service 368 | 369 | ## [0.3.1] - 2021-04-06 370 | 371 | ### Fixed 372 | 373 | - Use `DEFAULT_AUTO_FIELD` in `settings.py` instead of the lowercase variant 374 | 375 | ## [0.3.0] - 2021-04-06 376 | 377 | ### Changed 378 | 379 | - Update Django to `3.2` 380 | - Update TailwindCSS to `2.1.0` and enable the JIT compiler 381 | 382 | ### Removed 383 | 384 | - Remove Webpack's cache since the JIT compiler is pretty speedy as is 385 | 386 | ## [0.2.0] - 2021-03-17 387 | 388 | ### Changed 389 | 390 | - Replace `##` comments with `#` in the `run` script 391 | - Switch `OptimizeCSSAssetsPlugin` with `CssMinimizerPlugin` for Webpack 5 392 | - Replace deprecated Webpack 5 `file-loader` with `asset/resource` 393 | - Update flake8 from `3.8.4` to `3.9.0` 394 | 395 | ### Removed 396 | 397 | - Remove unnecessary `mkdir` for the pip cache dir and chown'ing a few directories 398 | - Unused `webpack` import in Webpack config 399 | 400 | ### Fixed 401 | 402 | - Make sure `public_collected/.keep` is never removed 403 | - Code styling issues in the Webpack config (single quotes, semi-colons, etc.) 404 | 405 | ## [0.1.0] - 2021-02-24 406 | 407 | ### Added 408 | 409 | - Everything! 410 | 411 | [Unreleased]: https://github.com/nickjj/docker-django-example/compare/0.11.0...HEAD 412 | [0.11.0]: https://github.com/nickjj/docker-django-example/compare/0.10.0...0.11.0 413 | [0.10.0]: https://github.com/nickjj/docker-django-example/compare/0.9.0...0.10.0 414 | [0.9.0]: https://github.com/nickjj/docker-django-example/compare/0.8.0...0.9.0 415 | [0.8.0]: https://github.com/nickjj/docker-django-example/compare/0.7.0...0.8.0 416 | [0.7.0]: https://github.com/nickjj/docker-django-example/compare/0.6.0...0.7.0 417 | [0.6.0]: https://github.com/nickjj/docker-django-example/compare/0.5.0...0.6.0 418 | [0.5.0]: https://github.com/nickjj/docker-django-example/compare/0.4.0...0.5.0 419 | [0.4.0]: https://github.com/nickjj/docker-django-example/compare/0.3.1...0.4.0 420 | [0.3.1]: https://github.com/nickjj/docker-django-example/compare/0.3.0...0.3.1 421 | [0.3.0]: https://github.com/nickjj/docker-django-example/compare/0.2.0...0.3.0 422 | [0.2.0]: https://github.com/nickjj/docker-django-example/compare/0.1.0...0.2.0 423 | [0.1.0]: https://github.com/nickjj/docker-django-example/releases/tag/0.1.0 424 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.16.0-bookworm-slim AS assets 2 | LABEL maintainer="Nick Janetakis " 3 | 4 | WORKDIR /app/assets 5 | 6 | ARG UID=1000 7 | ARG GID=1000 8 | 9 | RUN apt-get update \ 10 | && apt-get install -y --no-install-recommends build-essential \ 11 | && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ 12 | && apt-get clean \ 13 | && groupmod -g "${GID}" node && usermod -u "${UID}" -g "${GID}" node \ 14 | && mkdir -p /node_modules && chown node:node -R /node_modules /app 15 | 16 | USER node 17 | 18 | COPY --chown=node:node assets/package.json assets/*yarn* ./ 19 | 20 | RUN yarn install && yarn cache clean 21 | 22 | ARG NODE_ENV="production" 23 | ENV NODE_ENV="${NODE_ENV}" \ 24 | PATH="${PATH}:/node_modules/.bin" \ 25 | USER="node" 26 | 27 | COPY --chown=node:node . .. 28 | 29 | RUN if [ "${NODE_ENV}" != "development" ]; then \ 30 | ../run yarn:build:js && ../run yarn:build:css; else mkdir -p /app/public; fi 31 | 32 | CMD ["bash"] 33 | 34 | ############################################################################### 35 | 36 | FROM python:3.13.3-slim-bookworm AS app-build 37 | LABEL maintainer="Nick Janetakis " 38 | 39 | WORKDIR /app 40 | 41 | ARG UID=1000 42 | ARG GID=1000 43 | 44 | RUN apt-get update \ 45 | && apt-get install -y --no-install-recommends build-essential curl libpq-dev \ 46 | && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ 47 | && apt-get clean \ 48 | && groupadd -g "${GID}" python \ 49 | && useradd --create-home --no-log-init -u "${UID}" -g "${GID}" python \ 50 | && chown python:python -R /app 51 | 52 | COPY --from=ghcr.io/astral-sh/uv:0.6.9 /uv /uvx /usr/local/bin/ 53 | 54 | USER python 55 | 56 | COPY --chown=python:python pyproject.toml uv.lock* ./ 57 | COPY --chown=python:python bin/ ./bin 58 | 59 | ENV PYTHONUNBUFFERED="true" \ 60 | PYTHONPATH="." \ 61 | UV_COMPILE_BYTECODE=1 \ 62 | UV_PROJECT_ENVIRONMENT="/home/python/.local" \ 63 | PATH="${PATH}:/home/python/.local/bin" \ 64 | USER="python" 65 | 66 | RUN chmod 0755 bin/* && bin/uv-install 67 | 68 | CMD ["bash"] 69 | 70 | ############################################################################### 71 | 72 | FROM python:3.13.3-slim-bookworm AS app 73 | LABEL maintainer="Nick Janetakis " 74 | 75 | WORKDIR /app 76 | 77 | ARG UID=1000 78 | ARG GID=1000 79 | 80 | RUN apt-get update \ 81 | && apt-get install -y --no-install-recommends curl libpq-dev \ 82 | && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man \ 83 | && apt-get clean \ 84 | && groupadd -g "${GID}" python \ 85 | && useradd --create-home --no-log-init -u "${UID}" -g "${GID}" python \ 86 | && mkdir -p /public_collected public \ 87 | && chown python:python -R /public_collected /app 88 | 89 | USER python 90 | 91 | ARG DEBUG="false" 92 | ENV DEBUG="${DEBUG}" \ 93 | PYTHONUNBUFFERED="true" \ 94 | PYTHONPATH="." \ 95 | UV_PROJECT_ENVIRONMENT="/home/python/.local" \ 96 | PATH="${PATH}:/home/python/.local/bin" \ 97 | USER="python" 98 | 99 | COPY --chown=python:python --from=assets /app/public /public 100 | COPY --chown=python:python --from=app-build /home/python/.local /home/python/.local 101 | COPY --from=app-build /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/ 102 | COPY --chown=python:python . . 103 | 104 | WORKDIR /app/src 105 | 106 | RUN if [ "${DEBUG}" = "false" ]; then \ 107 | SECRET_KEY=dummyvalue python3 manage.py collectstatic --no-input; \ 108 | else mkdir -p /app/public_collected; fi 109 | 110 | ENTRYPOINT ["/app/bin/docker-entrypoint-web"] 111 | 112 | EXPOSE 8000 113 | 114 | CMD ["gunicorn", "-c", "python:config.gunicorn", "config.wsgi"] 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Nick Janetakis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐳 An example Django + Docker app 2 | 3 | You could use this example app as a base for your new project or as a guide to 4 | Dockerize your existing Django app. 5 | 6 | The example app is minimal but it wires up a number of things you might use in 7 | a real world Django app, but at the same time it's not loaded up with a million 8 | personal opinions. 9 | 10 | For the Docker bits, everything included is an accumulation of [Docker best 11 | practices](https://nickjanetakis.com/blog/best-practices-around-production-ready-web-apps-with-docker-compose) 12 | based on building and deploying dozens of assorted Dockerized web apps since 13 | late 2014. 14 | 15 | **This app is using Django 5.2.1 and Python 3.13.3**. The screenshot shows 16 | `X.X.X` since they get updated regularly: 17 | 18 | [![Screenshot](.github/docs/screenshot.jpg)](https://github.com/nickjj/docker-django-example/blob/main/.github/docs/screenshot.jpg?raw=true) 19 | 20 | ## 🧾 Table of contents 21 | 22 | - [Tech stack](#tech-stack) 23 | - [Notable opinions and extensions](#notable-opinions-and-extensions) 24 | - [Running this app](#running-this-app) 25 | - [Files of interest](#files-of-interest) 26 | - [`.env`](#env) 27 | - [`run`](#run) 28 | - [Running a script to automate renaming the project](#running-a-script-to-automate-renaming-the-project) 29 | - [Updating dependencies](#updating-dependencies) 30 | - [See a way to improve something?](#see-a-way-to-improve-something) 31 | - [Additional resources](#additional-resources) 32 | - [Learn more about Docker and Django](#learn-more-about-docker-and-django) 33 | - [Deploy to production](#deploy-to-production) 34 | - [About the author](#about-the-author) 35 | 36 | ## 🧬 Tech stack 37 | 38 | If you don't like some of these choices that's no problem, you can swap them 39 | out for something else on your own. 40 | 41 | ### Back-end 42 | 43 | - [PostgreSQL](https://www.postgresql.org/) 44 | - [Redis](https://redis.io/) 45 | - [Celery](https://github.com/celery/celery) 46 | 47 | ### Front-end 48 | 49 | - [esbuild](https://esbuild.github.io/) 50 | - [TailwindCSS](https://tailwindcss.com/) 51 | - [Heroicons](https://heroicons.com/) 52 | 53 | #### But what about JavaScript?! 54 | 55 | Picking a JS library is a very app specific decision because it depends on 56 | which library you like and it also depends on if your app is going to be 57 | mostly Django templates with sprinkles of JS or an API back-end. 58 | 59 | This isn't an exhaustive list but here's a few reasonable choices depending on 60 | how you're building your app: 61 | 62 | - 63 | - 64 | - 65 | - 66 | - 67 | - 68 | 69 | On the bright side with esbuild being set up you can use any (or none) of these 70 | solutions very easily. You could follow a specific library's installation 71 | guides to get up and running in no time. 72 | 73 | Personally I'm going to be using Hotwire Turbo + Stimulus in most newer 74 | projects. 75 | 76 | ## 🍣 Notable opinions and extensions 77 | 78 | Django is an opinionated framework and I've added a few extra opinions based on 79 | having Dockerized and deployed a number of Django projects. Here's a few (but 80 | not all) note worthy additions and changes. 81 | 82 | - **Packages and extensions**: 83 | - *[gunicorn](https://gunicorn.org/)* for an app server in both development and production 84 | - *[whitenoise](https://github.com/evansd/whitenoise)* for serving static files 85 | - *[django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar)* for displaying info about a request 86 | - **Linting and formatting**: 87 | - *[ruff](https://github.com/astral-sh/ruff)* is used to lint and format the code base 88 | - **Django apps**: 89 | - Add `pages` app to render a home page 90 | - Add `up` app to provide a few health check pages 91 | - **Config**: 92 | - Log to STDOUT so that Docker can consume and deal with log output 93 | - Extract a bunch of configuration settings into environment variables 94 | - Rename project directory from its custom name to `config/` 95 | - `src/config/settings.py` and the `.env` file handles configuration in all environments 96 | - **Front-end assets**: 97 | - `assets/` contains all your CSS, JS, images, fonts, etc. and is managed by esbuild 98 | - Custom `502.html` and `maintenance.html` pages 99 | - Generate favicons using modern best practices 100 | - **Django defaults that are changed**: 101 | - Use Redis as the default Cache back-end 102 | - Use signed cookies as the session back-end 103 | - `public/` is the static directory where Django will serve static files from 104 | - `public_collected/` is where `collectstatic` will write its files to 105 | 106 | Besides the Django app itself: 107 | 108 | - [uv](https://github.com/astral-sh/uv) is used for package management instead of `pip3` (builds on my machine are ~10x faster!) 109 | - Docker support has been added which would be any files having `*docker*` in 110 | its name 111 | - GitHub Actions have been set up 112 | 113 | ## 🚀 Running this app 114 | 115 | You'll need to have [Docker installed](https://docs.docker.com/get-docker/). 116 | It's available on Windows, macOS and most distros of Linux. If you're new to 117 | Docker and want to learn it in detail check out the [additional resources 118 | links](#learn-more-about-docker-and-django) near the bottom of this README. 119 | 120 | You'll also need to enable Docker Compose v2 support if you're using Docker 121 | Desktop. On native Linux without Docker Desktop you can [install it as a plugin 122 | to Docker](https://docs.docker.com/compose/install/linux/). It's been generally 123 | available for a while now and is stable. This project uses specific [Docker 124 | Compose v2 125 | features](https://nickjanetakis.com/blog/optional-depends-on-with-docker-compose-v2-20-2) 126 | that only work with Docker Compose v2 2.20.2+. 127 | 128 | If you're using Windows, it will be expected that you're following along inside 129 | of [WSL or WSL 130 | 2](https://nickjanetakis.com/blog/a-linux-dev-environment-on-windows-with-wsl-2-docker-desktop-and-more). 131 | That's because we're going to be running shell commands. You can always modify 132 | these commands for PowerShell if you want. 133 | 134 | #### Clone this repo anywhere you want and move into the directory: 135 | 136 | ```sh 137 | git clone https://github.com/nickjj/docker-django-example hellodjango 138 | cd hellodjango 139 | 140 | # Optionally checkout a specific tag, such as: git checkout 0.11.0 141 | ``` 142 | 143 | #### Copy an example .env file because the real one is git ignored: 144 | 145 | ```sh 146 | cp .env.example .env 147 | ``` 148 | 149 | #### Build everything: 150 | 151 | *The first time you run this it's going to take 5-10 minutes depending on your 152 | internet connection speed and computer's hardware specs. That's because it's 153 | going to download a few Docker images and build the Python + Yarn dependencies.* 154 | 155 | ```sh 156 | docker compose up --build 157 | ``` 158 | 159 | Now that everything is built and running we can treat it like any other Django 160 | app. 161 | 162 | Did you receive a `depends_on` "Additional property required is not allowed" 163 | error? Please update to at least Docker Compose v2.20.2+ or Docker Desktop 164 | 4.22.0+. 165 | 166 | Did you receive an error about a port being in use? Chances are it's because 167 | something on your machine is already running on port 8000. Check out the docs 168 | in the `.env` file for the `DOCKER_WEB_PORT_FORWARD` variable to fix this. 169 | 170 | Did you receive a permission denied error? Chances are you're running native 171 | Linux and your `uid:gid` aren't `1000:1000` (you can verify this by running 172 | `id`). Check out the docs in the `.env` file to customize the `UID` and `GID` 173 | variables to fix this. 174 | 175 | #### Setup the initial database: 176 | 177 | ```sh 178 | # You can run this from a 2nd terminal. 179 | ./run manage migrate 180 | ``` 181 | 182 | *We'll go over that `./run` script in a bit!* 183 | 184 | #### Check it out in a browser: 185 | 186 | Visit in your favorite browser. 187 | 188 | #### Linting the code base: 189 | 190 | ```sh 191 | # You should get no output (that means everything is operational). 192 | ./run lint 193 | ``` 194 | 195 | #### Formatting the code base: 196 | 197 | ```sh 198 | # You should see that everything is unchanged (it's all already formatted). 199 | ./run format 200 | ``` 201 | 202 | *There's also a `./run quality` command to run the above commands together.* 203 | 204 | #### Running the test suite: 205 | 206 | ```sh 207 | # You should see all passing tests. Warnings are typically ok. 208 | ./run manage test 209 | ``` 210 | 211 | #### Stopping everything: 212 | 213 | ```sh 214 | # Stop the containers and remove a few Docker related resources associated to this project. 215 | docker compose down 216 | ``` 217 | 218 | You can start things up again with `docker compose up` and unlike the first 219 | time it should only take seconds. 220 | 221 | ## 🔍 Files of interest 222 | 223 | I recommend checking out most files and searching the code base for `TODO:`, 224 | but please review the `.env` and `run` files before diving into the rest of the 225 | code and customizing it. Also, you should hold off on changing anything until 226 | we cover how to customize this example app's name with an automated script 227 | (coming up next in the docs). 228 | 229 | ### `.env` 230 | 231 | This file is ignored from version control so it will never be commit. There's a 232 | number of environment variables defined here that control certain options and 233 | behavior of the application. Everything is documented there. 234 | 235 | Feel free to add new variables as needed. This is where you should put all of 236 | your secrets as well as configuration that might change depending on your 237 | environment (specific dev boxes, CI, production, etc.). 238 | 239 | ### `run` 240 | 241 | You can run `./run` to get a list of commands and each command has 242 | documentation in the `run` file itself. 243 | 244 | It's a shell script that has a number of functions defined to help you interact 245 | with this project. It's basically a `Makefile` except with [less 246 | limitations](https://nickjanetakis.com/blog/replacing-make-with-a-shell-script-for-running-your-projects-tasks). 247 | For example as a shell script it allows us to pass any arguments to another 248 | program. 249 | 250 | This comes in handy to run various Docker commands because sometimes these 251 | commands can be a bit long to type. Feel free to add as many convenience 252 | functions as you want. This file's purpose is to make your experience better! 253 | 254 | *If you get tired of typing `./run` you can always create a shell alias with 255 | `alias run=./run` in your `~/.bash_aliases` or equivalent file. Then you'll be 256 | able to run `run` instead of `./run`.* 257 | 258 | ## ✨ Running a script to automate renaming the project 259 | 260 | The app is named `hello` right now but chances are your app will be a different 261 | name. Since the app is already created we'll need to do a find / replace on a 262 | few variants of the string "hello" and update a few Docker related resources. 263 | 264 | And by we I mean I created a zero dependency shell script that does all of the 265 | heavy lifting for you. All you have to do is run the script below. 266 | 267 | #### Run the rename-project script included in this repo: 268 | 269 | ```sh 270 | # The script takes 2 arguments. 271 | # 272 | # The first one is the lower case version of your app's name, such as myapp or 273 | # my_app depending on your preference. 274 | # 275 | # The second one is used for your app's module name. For example if you used 276 | # myapp or my_app for the first argument you would want to use MyApp here. 277 | bin/rename-project myapp MyApp 278 | ``` 279 | 280 | The [bin/rename-project 281 | script](https://github.com/nickjj/docker-django-example/blob/main/bin/rename-project) 282 | is going to: 283 | 284 | - Remove any Docker resources for your current project 285 | - Perform a number of find / replace actions 286 | - Optionally initialize a new git repo for you 287 | 288 | *Afterwards you can delete this script because its only purpose is to assist in 289 | helping you change this project's name without depending on any complicated 290 | project generator tools or 3rd party dependencies.* 291 | 292 | If you're not comfy running the script or it doesn't work for whatever reasons 293 | you can [check it 294 | out](https://github.com/nickjj/docker-django-example/blob/main/bin/rename-project) 295 | and perform the actions manually. It's mostly running a find / replace across 296 | files and then renaming a few directories and files. 297 | 298 | #### Start and setup the project: 299 | 300 | This won't take as long as before because Docker can re-use most things. We'll 301 | also need to setup our database since a new one will be created for us by 302 | Docker. 303 | 304 | ```sh 305 | docker compose up --build 306 | 307 | # Then in a 2nd terminal once it's up and ready. 308 | ./run manage migrate 309 | ``` 310 | 311 | #### Sanity check to make sure the tests still pass: 312 | 313 | It's always a good idea to make sure things are in a working state before 314 | adding custom changes. 315 | 316 | ```sh 317 | # You can run this from the same terminal as before. 318 | ./run quality 319 | ./run manage test 320 | ``` 321 | 322 | If everything passes now you can optionally `git add -A && git commit -m 323 | "Initial commit"` and start customizing your app. Alternatively you can wait 324 | until you develop more of your app before committing anything. It's up to you! 325 | 326 | #### Tying up a few loose ends: 327 | 328 | You'll probably want to create a fresh `CHANGELOG.md` file for your project. I 329 | like following the style guide at but feel free 330 | to use whichever style you prefer. 331 | 332 | Since this project is MIT licensed you should keep my name and email address in 333 | the `LICENSE` file to adhere to that license's agreement, but you can also add 334 | your name and email on a new line. 335 | 336 | If you happen to base your app off this example app or write about any of the 337 | code in this project it would be rad if you could credit this repo by linking 338 | to it. If you want to reference me directly please link to my site at 339 | . You don't have to do this, but it would be very 340 | much appreciated! 341 | 342 | ## 🛠 Updating dependencies 343 | 344 | You can run `./run uv:outdated` or `./run yarn:outdated` to get a list of 345 | outdated dependencies based on what you currently have installed. Once you've 346 | figured out what you want to update, go make those updates in your 347 | `pyproject.toml` and / or `package.json` file. 348 | 349 | Or, let's say you've customized your app and it's time to add a new dependency, 350 | either for Python or Node. 351 | 352 | #### In development: 353 | 354 | ##### Option 1 355 | 356 | 1. Directly edit `pyproject.toml` or `assets/package.json` to add your package 357 | 2. `./run deps:install` or `./run deps:install --no-build` 358 | - The `--no-build` option will only write out a new lock file without re-building your image 359 | 360 | ##### Option 2 361 | 362 | 1. Run `./run uv add mypackage --no-sync` or `run yarn add mypackage --no-lockfile` which will update your `pyproject.toml` or `assets/package.json` with the latest version of that package but not install it 363 | 2. The same step as step 2 from option 1 364 | 365 | Either option is fine, it's up to you based on what's more convenient at the 366 | time. You can modify the above workflows for updating an existing package or 367 | removing one as well. 368 | 369 | You can also access `uv` and `yarn` in Docker with `./run uv` and `./run yarn` 370 | after you've upped the project. 371 | 372 | #### In CI: 373 | 374 | You'll want to run `docker compose build` since it will use any existing lock 375 | files if they exist. You can also check out the complete CI test pipeline in 376 | the [run](https://github.com/nickjj/docker-django-example/blob/main/run) file 377 | under the `ci:test` function. 378 | 379 | #### In production: 380 | 381 | This is usually a non-issue since you'll be pulling down pre-built images from 382 | a Docker registry but if you decide to build your Docker images directly on 383 | your server you could run `docker compose build` as part of your deploy 384 | pipeline which is similar to how it would work in CI. 385 | 386 | ## 🤝 See a way to improve something? 387 | 388 | If you see anything that could be improved please open an issue or start a PR. 389 | Any help is much appreciated! 390 | 391 | ## 🌎 Additional resources 392 | 393 | Now that you have your app ready to go, it's time to build something cool! If 394 | you want to learn more about Docker, Django and deploying a Django app here's a 395 | couple of free and paid resources. There's Google too! 396 | 397 | ### Learn more about Docker and Django 398 | 399 | #### Official documentation 400 | 401 | - 402 | - 403 | 404 | #### Courses / books 405 | 406 | - [https://diveintodocker.com](https://diveintodocker.com?ref=docker-django-example) 407 | is a course I created which goes over the Docker and Docker Compose 408 | fundamentals 409 | - William Vincent has a bunch of [beginner and advanced Django 410 | books](https://gumroad.com/a/139727987). He also co-hosts the [Django 411 | Chat](https://djangochat.com/) podcast 412 | 413 | ### Deploy to production 414 | 415 | I'm creating an in-depth course related to deploying Dockerized web apps. If 416 | you want to get notified when it launches with a discount and potentially get 417 | free videos while the course is being developed then [sign up here to get 418 | notified](https://nickjanetakis.com/courses/deploy-to-production). 419 | 420 | ## 👀 About the author 421 | 422 | - Nick Janetakis | | [@nickjanetakis](https://twitter.com/nickjanetakis) 423 | 424 | I'm a self taught developer and have been freelancing for the last ~20 years. 425 | You can read about everything I've learned along the way on my site at 426 | [https://nickjanetakis.com](https://nickjanetakis.com/). 427 | 428 | There's hundreds of [blog posts](https://nickjanetakis.com/) and a couple 429 | of [video courses](https://nickjanetakis.com/courses) on web development and 430 | deployment topics. I also have a [podcast](https://runninginproduction.com) 431 | where I talk with folks about running web apps in production. 432 | -------------------------------------------------------------------------------- /assets/.yarnrc: -------------------------------------------------------------------------------- 1 | --modules-folder /node_modules 2 | -------------------------------------------------------------------------------- /assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss" source("/app"); 2 | -------------------------------------------------------------------------------- /assets/esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild' 2 | import copyStaticFiles from 'esbuild-copy-static-files' 3 | 4 | let minify = false 5 | let sourcemap = true 6 | let watch = true 7 | 8 | if (process.env.NODE_ENV === 'production') { 9 | minify = true 10 | sourcemap = false 11 | watch = false 12 | } 13 | 14 | const config = { 15 | entryPoints: ['./js/app.js'], 16 | outfile: '../public/js/app.js', 17 | bundle: true, 18 | minify: minify, 19 | sourcemap: sourcemap, 20 | plugins: [copyStaticFiles()], 21 | } 22 | 23 | if (watch) { 24 | let context = await esbuild.context({...config, logLevel: 'info'}) 25 | await context.watch() 26 | } else { 27 | esbuild.build(config) 28 | } 29 | -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/js/app.js -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "private": true, 4 | "dependencies": { 5 | "esbuild": "0.25.5", 6 | "esbuild-copy-static-files": "0.1.0", 7 | "tailwindcss": "4.1.8", 8 | "@tailwindcss/cli": "4.1.8", 9 | "@tailwindcss/postcss": "4.1.8" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/static/502.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Please Reload Your Browser 4 | 25 | 26 | 27 |

A New Version of This Site Was Just Released

28 |

It takes a few seconds to come back up on its own. Nice timing!

29 |

It should be fixed by the time you read this message. Try reloading your browser.

30 |

P.S., don't worry, your data is safely backed up!

31 |
32 |

Seeing this page for a while? Chances are automated tools have notified me of the issue and I'm working on it but if it doesn't self resolve soon check Twitter for updates at: @nickjanetakis

33 | 34 | 35 | -------------------------------------------------------------------------------- /assets/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /assets/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /assets/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/favicon-16x16.png -------------------------------------------------------------------------------- /assets/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/favicon-32x32.png -------------------------------------------------------------------------------- /assets/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/favicon.ico -------------------------------------------------------------------------------- /assets/static/images/django.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/images/django.png -------------------------------------------------------------------------------- /assets/static/maintenance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Down for Planned Maintenance 4 | 25 | 26 | 27 |

Down for Temporary Planned Maintenance

28 |

Reason: The server is being upgraded to make things faster for you.

29 |

If all goes well everything will be up and running by 2pm EST, please check back then.

30 |

P.S., don't worry, your data is safely backed up!

31 |
32 |

Status updates may be posted on Twitter at: @nickjanetakis

33 | 34 | 35 | -------------------------------------------------------------------------------- /assets/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/assets/static/mstile-150x150.png -------------------------------------------------------------------------------- /assets/static/robots.txt: -------------------------------------------------------------------------------- 1 | # TODO: This will block all robots from crawling your site. You probably don't 2 | # want this set in production, but could be useful for a test / staging server. 3 | User-agent: * 4 | Disallow: / 5 | -------------------------------------------------------------------------------- /assets/static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /assets/tailwind.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | -------------------------------------------------------------------------------- /assets/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@alloc/quick-lru@^5.2.0": 6 | version "5.2.0" 7 | resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" 8 | integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== 9 | 10 | "@ampproject/remapping@^2.3.0": 11 | version "2.3.0" 12 | resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" 13 | integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== 14 | dependencies: 15 | "@jridgewell/gen-mapping" "^0.3.5" 16 | "@jridgewell/trace-mapping" "^0.3.24" 17 | 18 | "@emnapi/core@^1.4.3": 19 | version "1.4.3" 20 | resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.3.tgz#9ac52d2d5aea958f67e52c40a065f51de59b77d6" 21 | integrity sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g== 22 | dependencies: 23 | "@emnapi/wasi-threads" "1.0.2" 24 | tslib "^2.4.0" 25 | 26 | "@emnapi/runtime@^1.4.3": 27 | version "1.4.3" 28 | resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.3.tgz#c0564665c80dc81c448adac23f9dfbed6c838f7d" 29 | integrity sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== 30 | dependencies: 31 | tslib "^2.4.0" 32 | 33 | "@emnapi/wasi-threads@1.0.2", "@emnapi/wasi-threads@^1.0.2": 34 | version "1.0.2" 35 | resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz#977f44f844eac7d6c138a415a123818c655f874c" 36 | integrity sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA== 37 | dependencies: 38 | tslib "^2.4.0" 39 | 40 | "@esbuild/aix-ppc64@0.25.5": 41 | version "0.25.5" 42 | resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz#4e0f91776c2b340e75558f60552195f6fad09f18" 43 | integrity sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== 44 | 45 | "@esbuild/android-arm64@0.25.5": 46 | version "0.25.5" 47 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz#bc766407f1718923f6b8079c8c61bf86ac3a6a4f" 48 | integrity sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== 49 | 50 | "@esbuild/android-arm@0.25.5": 51 | version "0.25.5" 52 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz#4290d6d3407bae3883ad2cded1081a234473ce26" 53 | integrity sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== 54 | 55 | "@esbuild/android-x64@0.25.5": 56 | version "0.25.5" 57 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.5.tgz#40c11d9cbca4f2406548c8a9895d321bc3b35eff" 58 | integrity sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== 59 | 60 | "@esbuild/darwin-arm64@0.25.5": 61 | version "0.25.5" 62 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz#49d8bf8b1df95f759ac81eb1d0736018006d7e34" 63 | integrity sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== 64 | 65 | "@esbuild/darwin-x64@0.25.5": 66 | version "0.25.5" 67 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz#e27a5d92a14886ef1d492fd50fc61a2d4d87e418" 68 | integrity sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== 69 | 70 | "@esbuild/freebsd-arm64@0.25.5": 71 | version "0.25.5" 72 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz#97cede59d638840ca104e605cdb9f1b118ba0b1c" 73 | integrity sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== 74 | 75 | "@esbuild/freebsd-x64@0.25.5": 76 | version "0.25.5" 77 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz#71c77812042a1a8190c3d581e140d15b876b9c6f" 78 | integrity sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== 79 | 80 | "@esbuild/linux-arm64@0.25.5": 81 | version "0.25.5" 82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz#f7b7c8f97eff8ffd2e47f6c67eb5c9765f2181b8" 83 | integrity sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== 84 | 85 | "@esbuild/linux-arm@0.25.5": 86 | version "0.25.5" 87 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz#2a0be71b6cd8201fa559aea45598dffabc05d911" 88 | integrity sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== 89 | 90 | "@esbuild/linux-ia32@0.25.5": 91 | version "0.25.5" 92 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz#763414463cd9ea6fa1f96555d2762f9f84c61783" 93 | integrity sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== 94 | 95 | "@esbuild/linux-loong64@0.25.5": 96 | version "0.25.5" 97 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz#428cf2213ff786a502a52c96cf29d1fcf1eb8506" 98 | integrity sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== 99 | 100 | "@esbuild/linux-mips64el@0.25.5": 101 | version "0.25.5" 102 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz#5cbcc7fd841b4cd53358afd33527cd394e325d96" 103 | integrity sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== 104 | 105 | "@esbuild/linux-ppc64@0.25.5": 106 | version "0.25.5" 107 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz#0d954ab39ce4f5e50f00c4f8c4fd38f976c13ad9" 108 | integrity sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== 109 | 110 | "@esbuild/linux-riscv64@0.25.5": 111 | version "0.25.5" 112 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz#0e7dd30730505abd8088321e8497e94b547bfb1e" 113 | integrity sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== 114 | 115 | "@esbuild/linux-s390x@0.25.5": 116 | version "0.25.5" 117 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz#5669af81327a398a336d7e40e320b5bbd6e6e72d" 118 | integrity sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== 119 | 120 | "@esbuild/linux-x64@0.25.5": 121 | version "0.25.5" 122 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz#b2357dd153aa49038967ddc1ffd90c68a9d2a0d4" 123 | integrity sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw== 124 | 125 | "@esbuild/netbsd-arm64@0.25.5": 126 | version "0.25.5" 127 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz#53b4dfb8fe1cee93777c9e366893bd3daa6ba63d" 128 | integrity sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== 129 | 130 | "@esbuild/netbsd-x64@0.25.5": 131 | version "0.25.5" 132 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz#a0206f6314ce7dc8713b7732703d0f58de1d1e79" 133 | integrity sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ== 134 | 135 | "@esbuild/openbsd-arm64@0.25.5": 136 | version "0.25.5" 137 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz#2a796c87c44e8de78001d808c77d948a21ec22fd" 138 | integrity sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== 139 | 140 | "@esbuild/openbsd-x64@0.25.5": 141 | version "0.25.5" 142 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz#28d0cd8909b7fa3953af998f2b2ed34f576728f0" 143 | integrity sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== 144 | 145 | "@esbuild/sunos-x64@0.25.5": 146 | version "0.25.5" 147 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz#a28164f5b997e8247d407e36c90d3fd5ddbe0dc5" 148 | integrity sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== 149 | 150 | "@esbuild/win32-arm64@0.25.5": 151 | version "0.25.5" 152 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz#6eadbead38e8bd12f633a5190e45eff80e24007e" 153 | integrity sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== 154 | 155 | "@esbuild/win32-ia32@0.25.5": 156 | version "0.25.5" 157 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz#bab6288005482f9ed2adb9ded7e88eba9a62cc0d" 158 | integrity sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== 159 | 160 | "@esbuild/win32-x64@0.25.5": 161 | version "0.25.5" 162 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz#7fc114af5f6563f19f73324b5d5ff36ece0803d1" 163 | integrity sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g== 164 | 165 | "@isaacs/fs-minipass@^4.0.0": 166 | version "4.0.1" 167 | resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" 168 | integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== 169 | dependencies: 170 | minipass "^7.0.4" 171 | 172 | "@jridgewell/gen-mapping@^0.3.5": 173 | version "0.3.8" 174 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" 175 | integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== 176 | dependencies: 177 | "@jridgewell/set-array" "^1.2.1" 178 | "@jridgewell/sourcemap-codec" "^1.4.10" 179 | "@jridgewell/trace-mapping" "^0.3.24" 180 | 181 | "@jridgewell/resolve-uri@^3.1.0": 182 | version "3.1.2" 183 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" 184 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 185 | 186 | "@jridgewell/set-array@^1.2.1": 187 | version "1.2.1" 188 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" 189 | integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== 190 | 191 | "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": 192 | version "1.5.0" 193 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" 194 | integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== 195 | 196 | "@jridgewell/trace-mapping@^0.3.24": 197 | version "0.3.25" 198 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" 199 | integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== 200 | dependencies: 201 | "@jridgewell/resolve-uri" "^3.1.0" 202 | "@jridgewell/sourcemap-codec" "^1.4.14" 203 | 204 | "@napi-rs/wasm-runtime@^0.2.10": 205 | version "0.2.10" 206 | resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz#f3b7109419c6670000b2401e0c778b98afc25f84" 207 | integrity sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ== 208 | dependencies: 209 | "@emnapi/core" "^1.4.3" 210 | "@emnapi/runtime" "^1.4.3" 211 | "@tybys/wasm-util" "^0.9.0" 212 | 213 | "@parcel/watcher-android-arm64@2.5.1": 214 | version "2.5.1" 215 | resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" 216 | integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== 217 | 218 | "@parcel/watcher-darwin-arm64@2.5.1": 219 | version "2.5.1" 220 | resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" 221 | integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== 222 | 223 | "@parcel/watcher-darwin-x64@2.5.1": 224 | version "2.5.1" 225 | resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" 226 | integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== 227 | 228 | "@parcel/watcher-freebsd-x64@2.5.1": 229 | version "2.5.1" 230 | resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" 231 | integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== 232 | 233 | "@parcel/watcher-linux-arm-glibc@2.5.1": 234 | version "2.5.1" 235 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" 236 | integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== 237 | 238 | "@parcel/watcher-linux-arm-musl@2.5.1": 239 | version "2.5.1" 240 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" 241 | integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== 242 | 243 | "@parcel/watcher-linux-arm64-glibc@2.5.1": 244 | version "2.5.1" 245 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" 246 | integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== 247 | 248 | "@parcel/watcher-linux-arm64-musl@2.5.1": 249 | version "2.5.1" 250 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" 251 | integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== 252 | 253 | "@parcel/watcher-linux-x64-glibc@2.5.1": 254 | version "2.5.1" 255 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" 256 | integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== 257 | 258 | "@parcel/watcher-linux-x64-musl@2.5.1": 259 | version "2.5.1" 260 | resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" 261 | integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== 262 | 263 | "@parcel/watcher-win32-arm64@2.5.1": 264 | version "2.5.1" 265 | resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" 266 | integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== 267 | 268 | "@parcel/watcher-win32-ia32@2.5.1": 269 | version "2.5.1" 270 | resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" 271 | integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== 272 | 273 | "@parcel/watcher-win32-x64@2.5.1": 274 | version "2.5.1" 275 | resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" 276 | integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== 277 | 278 | "@parcel/watcher@^2.5.1": 279 | version "2.5.1" 280 | resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" 281 | integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== 282 | dependencies: 283 | detect-libc "^1.0.3" 284 | is-glob "^4.0.3" 285 | micromatch "^4.0.5" 286 | node-addon-api "^7.0.0" 287 | optionalDependencies: 288 | "@parcel/watcher-android-arm64" "2.5.1" 289 | "@parcel/watcher-darwin-arm64" "2.5.1" 290 | "@parcel/watcher-darwin-x64" "2.5.1" 291 | "@parcel/watcher-freebsd-x64" "2.5.1" 292 | "@parcel/watcher-linux-arm-glibc" "2.5.1" 293 | "@parcel/watcher-linux-arm-musl" "2.5.1" 294 | "@parcel/watcher-linux-arm64-glibc" "2.5.1" 295 | "@parcel/watcher-linux-arm64-musl" "2.5.1" 296 | "@parcel/watcher-linux-x64-glibc" "2.5.1" 297 | "@parcel/watcher-linux-x64-musl" "2.5.1" 298 | "@parcel/watcher-win32-arm64" "2.5.1" 299 | "@parcel/watcher-win32-ia32" "2.5.1" 300 | "@parcel/watcher-win32-x64" "2.5.1" 301 | 302 | "@tailwindcss/cli@4.1.8": 303 | version "4.1.8" 304 | resolved "https://registry.yarnpkg.com/@tailwindcss/cli/-/cli-4.1.8.tgz#83a29061d1dc12d69e4070dfb0bda4a622181a1d" 305 | integrity sha512-+6lkjXSr/68zWiabK3mVYVHmOq/SAHjJ13mR8spyB4LgUWZbWzU9kCSErlAUo+gK5aVfgqe8kY6Ltz9+nz5XYA== 306 | dependencies: 307 | "@parcel/watcher" "^2.5.1" 308 | "@tailwindcss/node" "4.1.8" 309 | "@tailwindcss/oxide" "4.1.8" 310 | enhanced-resolve "^5.18.1" 311 | mri "^1.2.0" 312 | picocolors "^1.1.1" 313 | tailwindcss "4.1.8" 314 | 315 | "@tailwindcss/node@4.1.8": 316 | version "4.1.8" 317 | resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.1.8.tgz#e29187abec6194ce1e9f072208c62116a79a129b" 318 | integrity sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q== 319 | dependencies: 320 | "@ampproject/remapping" "^2.3.0" 321 | enhanced-resolve "^5.18.1" 322 | jiti "^2.4.2" 323 | lightningcss "1.30.1" 324 | magic-string "^0.30.17" 325 | source-map-js "^1.2.1" 326 | tailwindcss "4.1.8" 327 | 328 | "@tailwindcss/oxide-android-arm64@4.1.8": 329 | version "4.1.8" 330 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz#4cb4b464636fc7e3154a1bb7df38a828291b3e9a" 331 | integrity sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg== 332 | 333 | "@tailwindcss/oxide-darwin-arm64@4.1.8": 334 | version "4.1.8" 335 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz#b0b8c02745f76aea683c30818e249d62821864b8" 336 | integrity sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A== 337 | 338 | "@tailwindcss/oxide-darwin-x64@4.1.8": 339 | version "4.1.8" 340 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz#d0f3fa4c3bde21a772e29e31c9739d91db79de12" 341 | integrity sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw== 342 | 343 | "@tailwindcss/oxide-freebsd-x64@4.1.8": 344 | version "4.1.8" 345 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz#545c94c941007ed1aa2e449465501b70d59cb3da" 346 | integrity sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg== 347 | 348 | "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8": 349 | version "4.1.8" 350 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz#e1bdbf63a179081669b8cd1c9523889774760eb9" 351 | integrity sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ== 352 | 353 | "@tailwindcss/oxide-linux-arm64-gnu@4.1.8": 354 | version "4.1.8" 355 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz#8d28093bbd43bdae771a2dcca720e926baa57093" 356 | integrity sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q== 357 | 358 | "@tailwindcss/oxide-linux-arm64-musl@4.1.8": 359 | version "4.1.8" 360 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz#cc6cece814d813885ead9cd8b9d55aeb3db56c97" 361 | integrity sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ== 362 | 363 | "@tailwindcss/oxide-linux-x64-gnu@4.1.8": 364 | version "4.1.8" 365 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz#4cac14fa71382574773fb7986d9f0681ad89e3de" 366 | integrity sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g== 367 | 368 | "@tailwindcss/oxide-linux-x64-musl@4.1.8": 369 | version "4.1.8" 370 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz#e085f1ccbc8f97625773a6a3afc2a6f88edf59da" 371 | integrity sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg== 372 | 373 | "@tailwindcss/oxide-wasm32-wasi@4.1.8": 374 | version "4.1.8" 375 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz#c5e19fffe67f25cabf12a357bba4e87128151ea0" 376 | integrity sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg== 377 | dependencies: 378 | "@emnapi/core" "^1.4.3" 379 | "@emnapi/runtime" "^1.4.3" 380 | "@emnapi/wasi-threads" "^1.0.2" 381 | "@napi-rs/wasm-runtime" "^0.2.10" 382 | "@tybys/wasm-util" "^0.9.0" 383 | tslib "^2.8.0" 384 | 385 | "@tailwindcss/oxide-win32-arm64-msvc@4.1.8": 386 | version "4.1.8" 387 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz#77521f23f91604c587736927fd2cb526667b7344" 388 | integrity sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA== 389 | 390 | "@tailwindcss/oxide-win32-x64-msvc@4.1.8": 391 | version "4.1.8" 392 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz#55c876ab35f8779d1dceec61483cd9834d7365ac" 393 | integrity sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ== 394 | 395 | "@tailwindcss/oxide@4.1.8": 396 | version "4.1.8" 397 | resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.1.8.tgz#b7a3df10c6c47ac5a3ac9976ad334732c4870d16" 398 | integrity sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A== 399 | dependencies: 400 | detect-libc "^2.0.4" 401 | tar "^7.4.3" 402 | optionalDependencies: 403 | "@tailwindcss/oxide-android-arm64" "4.1.8" 404 | "@tailwindcss/oxide-darwin-arm64" "4.1.8" 405 | "@tailwindcss/oxide-darwin-x64" "4.1.8" 406 | "@tailwindcss/oxide-freebsd-x64" "4.1.8" 407 | "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.8" 408 | "@tailwindcss/oxide-linux-arm64-gnu" "4.1.8" 409 | "@tailwindcss/oxide-linux-arm64-musl" "4.1.8" 410 | "@tailwindcss/oxide-linux-x64-gnu" "4.1.8" 411 | "@tailwindcss/oxide-linux-x64-musl" "4.1.8" 412 | "@tailwindcss/oxide-wasm32-wasi" "4.1.8" 413 | "@tailwindcss/oxide-win32-arm64-msvc" "4.1.8" 414 | "@tailwindcss/oxide-win32-x64-msvc" "4.1.8" 415 | 416 | "@tailwindcss/postcss@4.1.8": 417 | version "4.1.8" 418 | resolved "https://registry.yarnpkg.com/@tailwindcss/postcss/-/postcss-4.1.8.tgz#b12374b49f3accd9bd902a4324d124e67803350f" 419 | integrity sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw== 420 | dependencies: 421 | "@alloc/quick-lru" "^5.2.0" 422 | "@tailwindcss/node" "4.1.8" 423 | "@tailwindcss/oxide" "4.1.8" 424 | postcss "^8.4.41" 425 | tailwindcss "4.1.8" 426 | 427 | "@tybys/wasm-util@^0.9.0": 428 | version "0.9.0" 429 | resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz#3e75eb00604c8d6db470bf18c37b7d984a0e3355" 430 | integrity sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw== 431 | dependencies: 432 | tslib "^2.4.0" 433 | 434 | braces@^3.0.3: 435 | version "3.0.3" 436 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 437 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 438 | dependencies: 439 | fill-range "^7.1.1" 440 | 441 | chownr@^3.0.0: 442 | version "3.0.0" 443 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" 444 | integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== 445 | 446 | detect-libc@^1.0.3: 447 | version "1.0.3" 448 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 449 | integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== 450 | 451 | detect-libc@^2.0.3: 452 | version "2.0.3" 453 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" 454 | integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== 455 | 456 | detect-libc@^2.0.4: 457 | version "2.0.4" 458 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" 459 | integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== 460 | 461 | enhanced-resolve@^5.18.1: 462 | version "5.18.1" 463 | resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" 464 | integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== 465 | dependencies: 466 | graceful-fs "^4.2.4" 467 | tapable "^2.2.0" 468 | 469 | esbuild-copy-static-files@0.1.0: 470 | version "0.1.0" 471 | resolved "https://registry.yarnpkg.com/esbuild-copy-static-files/-/esbuild-copy-static-files-0.1.0.tgz#4bb4987b5b554d2fc122a45f077d74663b4dbcf0" 472 | integrity sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w== 473 | 474 | esbuild@0.25.5: 475 | version "0.25.5" 476 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.5.tgz#71075054993fdfae76c66586f9b9c1f8d7edd430" 477 | integrity sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ== 478 | optionalDependencies: 479 | "@esbuild/aix-ppc64" "0.25.5" 480 | "@esbuild/android-arm" "0.25.5" 481 | "@esbuild/android-arm64" "0.25.5" 482 | "@esbuild/android-x64" "0.25.5" 483 | "@esbuild/darwin-arm64" "0.25.5" 484 | "@esbuild/darwin-x64" "0.25.5" 485 | "@esbuild/freebsd-arm64" "0.25.5" 486 | "@esbuild/freebsd-x64" "0.25.5" 487 | "@esbuild/linux-arm" "0.25.5" 488 | "@esbuild/linux-arm64" "0.25.5" 489 | "@esbuild/linux-ia32" "0.25.5" 490 | "@esbuild/linux-loong64" "0.25.5" 491 | "@esbuild/linux-mips64el" "0.25.5" 492 | "@esbuild/linux-ppc64" "0.25.5" 493 | "@esbuild/linux-riscv64" "0.25.5" 494 | "@esbuild/linux-s390x" "0.25.5" 495 | "@esbuild/linux-x64" "0.25.5" 496 | "@esbuild/netbsd-arm64" "0.25.5" 497 | "@esbuild/netbsd-x64" "0.25.5" 498 | "@esbuild/openbsd-arm64" "0.25.5" 499 | "@esbuild/openbsd-x64" "0.25.5" 500 | "@esbuild/sunos-x64" "0.25.5" 501 | "@esbuild/win32-arm64" "0.25.5" 502 | "@esbuild/win32-ia32" "0.25.5" 503 | "@esbuild/win32-x64" "0.25.5" 504 | 505 | fill-range@^7.1.1: 506 | version "7.1.1" 507 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 508 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 509 | dependencies: 510 | to-regex-range "^5.0.1" 511 | 512 | graceful-fs@^4.2.4: 513 | version "4.2.11" 514 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" 515 | integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== 516 | 517 | is-extglob@^2.1.1: 518 | version "2.1.1" 519 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 520 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 521 | 522 | is-glob@^4.0.3: 523 | version "4.0.3" 524 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 525 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 526 | dependencies: 527 | is-extglob "^2.1.1" 528 | 529 | is-number@^7.0.0: 530 | version "7.0.0" 531 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 532 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 533 | 534 | jiti@^2.4.2: 535 | version "2.4.2" 536 | resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" 537 | integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== 538 | 539 | lightningcss-darwin-arm64@1.30.1: 540 | version "1.30.1" 541 | resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz#3d47ce5e221b9567c703950edf2529ca4a3700ae" 542 | integrity sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== 543 | 544 | lightningcss-darwin-x64@1.30.1: 545 | version "1.30.1" 546 | resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz#e81105d3fd6330860c15fe860f64d39cff5fbd22" 547 | integrity sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== 548 | 549 | lightningcss-freebsd-x64@1.30.1: 550 | version "1.30.1" 551 | resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz#a0e732031083ff9d625c5db021d09eb085af8be4" 552 | integrity sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== 553 | 554 | lightningcss-linux-arm-gnueabihf@1.30.1: 555 | version "1.30.1" 556 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz#1f5ecca6095528ddb649f9304ba2560c72474908" 557 | integrity sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== 558 | 559 | lightningcss-linux-arm64-gnu@1.30.1: 560 | version "1.30.1" 561 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz#eee7799726103bffff1e88993df726f6911ec009" 562 | integrity sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== 563 | 564 | lightningcss-linux-arm64-musl@1.30.1: 565 | version "1.30.1" 566 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz#f2e4b53f42892feeef8f620cbb889f7c064a7dfe" 567 | integrity sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== 568 | 569 | lightningcss-linux-x64-gnu@1.30.1: 570 | version "1.30.1" 571 | resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz#2fc7096224bc000ebb97eea94aea248c5b0eb157" 572 | integrity sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== 573 | 574 | lightningcss-linux-x64-musl@1.30.1: 575 | version "1.30.1" 576 | resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz#66dca2b159fd819ea832c44895d07e5b31d75f26" 577 | integrity sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== 578 | 579 | lightningcss-win32-arm64-msvc@1.30.1: 580 | version "1.30.1" 581 | resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz#7d8110a19d7c2d22bfdf2f2bb8be68e7d1b69039" 582 | integrity sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== 583 | 584 | lightningcss-win32-x64-msvc@1.30.1: 585 | version "1.30.1" 586 | resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz#fd7dd008ea98494b85d24b4bea016793f2e0e352" 587 | integrity sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== 588 | 589 | lightningcss@1.30.1: 590 | version "1.30.1" 591 | resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.30.1.tgz#78e979c2d595bfcb90d2a8c0eb632fe6c5bfed5d" 592 | integrity sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== 593 | dependencies: 594 | detect-libc "^2.0.3" 595 | optionalDependencies: 596 | lightningcss-darwin-arm64 "1.30.1" 597 | lightningcss-darwin-x64 "1.30.1" 598 | lightningcss-freebsd-x64 "1.30.1" 599 | lightningcss-linux-arm-gnueabihf "1.30.1" 600 | lightningcss-linux-arm64-gnu "1.30.1" 601 | lightningcss-linux-arm64-musl "1.30.1" 602 | lightningcss-linux-x64-gnu "1.30.1" 603 | lightningcss-linux-x64-musl "1.30.1" 604 | lightningcss-win32-arm64-msvc "1.30.1" 605 | lightningcss-win32-x64-msvc "1.30.1" 606 | 607 | magic-string@^0.30.17: 608 | version "0.30.17" 609 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" 610 | integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== 611 | dependencies: 612 | "@jridgewell/sourcemap-codec" "^1.5.0" 613 | 614 | micromatch@^4.0.5: 615 | version "4.0.8" 616 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" 617 | integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== 618 | dependencies: 619 | braces "^3.0.3" 620 | picomatch "^2.3.1" 621 | 622 | minipass@^7.0.4, minipass@^7.1.2: 623 | version "7.1.2" 624 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" 625 | integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== 626 | 627 | minizlib@^3.0.1: 628 | version "3.0.2" 629 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.2.tgz#f33d638eb279f664439aa38dc5f91607468cb574" 630 | integrity sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== 631 | dependencies: 632 | minipass "^7.1.2" 633 | 634 | mkdirp@^3.0.1: 635 | version "3.0.1" 636 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" 637 | integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== 638 | 639 | mri@^1.2.0: 640 | version "1.2.0" 641 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" 642 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== 643 | 644 | nanoid@^3.3.8: 645 | version "3.3.8" 646 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" 647 | integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== 648 | 649 | node-addon-api@^7.0.0: 650 | version "7.1.1" 651 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" 652 | integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== 653 | 654 | picocolors@^1.1.1: 655 | version "1.1.1" 656 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" 657 | integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== 658 | 659 | picomatch@^2.3.1: 660 | version "2.3.1" 661 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 662 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 663 | 664 | postcss@^8.4.41: 665 | version "8.5.1" 666 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214" 667 | integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ== 668 | dependencies: 669 | nanoid "^3.3.8" 670 | picocolors "^1.1.1" 671 | source-map-js "^1.2.1" 672 | 673 | source-map-js@^1.2.1: 674 | version "1.2.1" 675 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" 676 | integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== 677 | 678 | tailwindcss@4.1.8: 679 | version "4.1.8" 680 | resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.1.8.tgz#5d66d095ee7d82f03d6dbc6158bc248e064a5c05" 681 | integrity sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og== 682 | 683 | tapable@^2.2.0: 684 | version "2.2.1" 685 | resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" 686 | integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== 687 | 688 | tar@^7.4.3: 689 | version "7.4.3" 690 | resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" 691 | integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== 692 | dependencies: 693 | "@isaacs/fs-minipass" "^4.0.0" 694 | chownr "^3.0.0" 695 | minipass "^7.1.2" 696 | minizlib "^3.0.1" 697 | mkdirp "^3.0.1" 698 | yallist "^5.0.0" 699 | 700 | to-regex-range@^5.0.1: 701 | version "5.0.1" 702 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 703 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 704 | dependencies: 705 | is-number "^7.0.0" 706 | 707 | tslib@^2.4.0, tslib@^2.8.0: 708 | version "2.8.1" 709 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" 710 | integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== 711 | 712 | yallist@^5.0.0: 713 | version "5.0.0" 714 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" 715 | integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== 716 | -------------------------------------------------------------------------------- /bin/docker-entrypoint-web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Always keep this here as it ensures your latest built assets make their way 6 | # into your volume persisted public directory. 7 | cp -r /public_collected /app 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /bin/rename-project: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | APP_NAME="${1}" 6 | MODULE_NAME="${2}" 7 | 8 | FIND_APP_NAME="hello" 9 | FIND_MODULE_NAME="Hello" 10 | FIND_FRAMEWORK="django" 11 | 12 | if [ -z "${APP_NAME}" ] || [ -z "${MODULE_NAME}" ]; then 13 | echo "You must supply both an app and module name, example: ${0} myapp MyApp" 14 | exit 1 15 | fi 16 | 17 | if [ "${APP_NAME}" = "${FIND_APP_NAME}" ]; then 18 | echo "Your new app name must be different than the current app name" 19 | exit 1 20 | fi 21 | 22 | cat </dev/null; then 8 | uv lock 9 | fi 10 | 11 | # Otherwise, use the existing lock file exactly how it is defined. 12 | uv sync --frozen --no-install-project 13 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | x-app: &default-app 2 | build: 3 | context: "." 4 | target: "app" 5 | args: 6 | - "UID=${UID:-1000}" 7 | - "GID=${GID:-1000}" 8 | - "DEBUG=${DEBUG:-false}" 9 | - "NODE_ENV=${NODE_ENV:-production}" 10 | depends_on: 11 | postgres: 12 | condition: "service_started" 13 | required: false 14 | redis: 15 | condition: "service_started" 16 | required: false 17 | env_file: 18 | - ".env" 19 | restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" 20 | stop_grace_period: "3s" 21 | tty: true 22 | volumes: 23 | - "${DOCKER_WEB_VOLUME:-./public_collected:/app/public_collected}" 24 | 25 | x-assets: &default-assets 26 | build: 27 | context: "." 28 | target: "assets" 29 | args: 30 | - "UID=${UID:-1000}" 31 | - "GID=${GID:-1000}" 32 | - "NODE_ENV=${NODE_ENV:-production}" 33 | env_file: 34 | - ".env" 35 | profiles: ["assets"] 36 | restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" 37 | stop_grace_period: "0" 38 | tty: true 39 | volumes: 40 | - ".:/app" 41 | 42 | services: 43 | postgres: 44 | deploy: 45 | resources: 46 | limits: 47 | cpus: "${DOCKER_POSTGRES_CPUS:-0}" 48 | memory: "${DOCKER_POSTGRES_MEMORY:-0}" 49 | environment: 50 | POSTGRES_USER: "${POSTGRES_USER}" 51 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" 52 | # POSTGRES_DB: "${POSTGRES_DB}" 53 | image: "postgres:17.5-bookworm" 54 | profiles: ["postgres"] 55 | restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" 56 | stop_grace_period: "3s" 57 | volumes: 58 | - "postgres:/var/lib/postgresql/data" 59 | 60 | redis: 61 | deploy: 62 | resources: 63 | limits: 64 | cpus: "${DOCKER_REDIS_CPUS:-0}" 65 | memory: "${DOCKER_REDIS_MEMORY:-0}" 66 | image: "redis:8.0.2-bookworm" 67 | profiles: ["redis"] 68 | restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" 69 | stop_grace_period: "3s" 70 | volumes: 71 | - "redis:/data" 72 | 73 | web: 74 | <<: *default-app 75 | deploy: 76 | resources: 77 | limits: 78 | cpus: "${DOCKER_WEB_CPUS:-0}" 79 | memory: "${DOCKER_WEB_MEMORY:-0}" 80 | healthcheck: 81 | test: "${DOCKER_WEB_HEALTHCHECK_TEST:-curl localhost:8000/up}" 82 | interval: "60s" 83 | timeout: "3s" 84 | start_period: "5s" 85 | retries: 3 86 | ports: 87 | - "${DOCKER_WEB_PORT_FORWARD:-127.0.0.1:8000}:${PORT:-8000}" 88 | profiles: ["web"] 89 | 90 | worker: 91 | <<: *default-app 92 | command: celery -A config worker -l "${CELERY_LOG_LEVEL:-info}" 93 | entrypoint: [] 94 | deploy: 95 | resources: 96 | limits: 97 | cpus: "${DOCKER_WORKER_CPUS:-0}" 98 | memory: "${DOCKER_WORKER_MEMORY:-0}" 99 | profiles: ["worker"] 100 | 101 | js: 102 | <<: *default-assets 103 | command: "../run yarn:build:js" 104 | 105 | css: 106 | <<: *default-assets 107 | command: "../run yarn:build:css" 108 | 109 | volumes: 110 | postgres: {} 111 | redis: {} 112 | -------------------------------------------------------------------------------- /public/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/public/.keep -------------------------------------------------------------------------------- /public_collected/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/public_collected/.keep -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "hello" 3 | version = "0.1.0" 4 | description = "An example Django app running in Docker." 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "django==5.2.1", 9 | "celery==5.5.3", 10 | "django-debug-toolbar==5.2.0", 11 | "gunicorn==23.0.0", 12 | "psycopg==3.2.9", 13 | "redis==6.2.0", 14 | "ruff==0.11.12", 15 | "setuptools==80.9.0", 16 | "whitenoise==6.9.0", 17 | ] 18 | 19 | [tool.ruff] 20 | line-length = 79 21 | 22 | [tool.ruff.lint] 23 | extend-select = ["I", "SIM"] 24 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | 6 | DC="${DC:-exec}" 7 | 8 | # If we're running in CI we need to disable TTY allocation for docker compose 9 | # commands that enable it by default, such as exec and run. 10 | TTY="${TTY:-}" 11 | if [[ ! -t 1 ]]; then 12 | TTY="-T" 13 | fi 14 | 15 | # ----------------------------------------------------------------------------- 16 | # Helper functions start with _ and aren't listed in this script's help menu. 17 | # ----------------------------------------------------------------------------- 18 | 19 | _dc() { 20 | # shellcheck disable=SC2086 21 | docker compose "${DC}" ${TTY} "${@}" 22 | } 23 | 24 | _dc_run() { 25 | DC="run" _dc --no-deps --rm "${@}" 26 | } 27 | 28 | # ----------------------------------------------------------------------------- 29 | 30 | cmd() { 31 | # Run any command you want in the web container 32 | _dc web "${@}" 33 | } 34 | 35 | manage() { 36 | # Run any manage.py commands 37 | 38 | # We need to collectstatic before we run our tests. 39 | if [ "${1-''}" == "test" ]; then 40 | cmd python3 manage.py collectstatic --no-input 41 | fi 42 | 43 | cmd python3 manage.py "${@}" 44 | } 45 | 46 | lint:dockerfile() { 47 | # Lint Dockerfile 48 | docker container run --rm -i \ 49 | -v "${PWD}/.hadolint.yaml:/.config/hadolint.yaml" \ 50 | hadolint/hadolint hadolint "${@}" - /dev/null 2>&1; then 58 | local cmd=(docker container run --rm -i -v "${PWD}:/mnt" koalaman/shellcheck:stable) 59 | fi 60 | 61 | find . -type f \ 62 | ! -path "./.git/*" \ 63 | ! -path "./.ruff_cache/*" \ 64 | ! -path "./.pytest_cache/*" \ 65 | ! -path "./assets/*" \ 66 | ! -path "./public/*" \ 67 | ! -path "./public_collected/*" \ 68 | -exec grep --quiet '^#!.*sh' {} \; -exec "${cmd[@]}" {} + 69 | } 70 | 71 | lint() { 72 | # Lint Python code 73 | cmd ruff check "${@}" 74 | } 75 | 76 | format:shell() { 77 | # Format shell scripts 78 | local cmd=(shfmt) 79 | 80 | if ! command -v shfmt >/dev/null 2>&1; then 81 | local cmd=(docker container run --rm -i -v "${PWD}:/mnt" -u "$(id -u):$(id -g)" -w /mnt mvdan/shfmt:v3) 82 | fi 83 | 84 | local maybe_write=("--write") 85 | 86 | for arg in "${@}"; do 87 | if [ "${arg}" == "-d" ] || [ "${arg}" == "--diff" ]; then 88 | unset "maybe_write[0]" 89 | fi 90 | done 91 | 92 | "${cmd[@]}" "${maybe_write[@]}" "${@}" . 93 | } 94 | 95 | format() { 96 | # Format Python code 97 | cmd ruff check --fix 98 | cmd ruff format "${@}" 99 | } 100 | 101 | quality() { 102 | # Perform all code quality commands together 103 | lint:dockerfile 104 | lint:shell 105 | lint 106 | 107 | format:shell 108 | format 109 | } 110 | 111 | secret() { 112 | # Generate a random secret that can be used for your SECRET_KEY and more 113 | cmd python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" 114 | } 115 | 116 | shell() { 117 | # Start a shell session in the web container 118 | cmd bash "${@}" 119 | } 120 | 121 | psql() { 122 | # Connect to PostgreSQL 123 | # shellcheck disable=SC1091 124 | . .env 125 | _dc postgres psql -U "${POSTGRES_USER}" "${@}" 126 | } 127 | 128 | redis-cli() { 129 | # Connect to Redis 130 | _dc redis redis-cli "${@}" 131 | } 132 | 133 | deps:install() { 134 | # Install back-end and / or front-end dependencies 135 | local no_build="${1:-}" 136 | 137 | [ -z "${no_build}" ] && docker compose down && docker compose build 138 | 139 | _dc_run js yarn install 140 | _dc_run web bash -c "cd .. && bin/uv-install" 141 | } 142 | 143 | uv() { 144 | # Run any uv commands 145 | cmd uv "${@}" 146 | } 147 | 148 | uv:outdated() { 149 | # List any installed packages that are outdated 150 | _dc_run web uv tree --outdated --depth 1 "${@}" 151 | } 152 | 153 | yarn() { 154 | # Run any yarn commands 155 | _dc js yarn "${@}" 156 | } 157 | 158 | yarn:outdated() { 159 | # List any installed packages that are outdated 160 | _dc_run js yarn outdated 161 | } 162 | 163 | yarn:build:js() { 164 | # Build JS assets, this is meant to be run from within the assets container 165 | mkdir -p ../public/js 166 | node esbuild.config.mjs 167 | } 168 | 169 | yarn:build:css() { 170 | # Build CSS assets, this is meant to be run from within the assets container 171 | local args=() 172 | 173 | if [ "${NODE_ENV:-}" == "production" ]; then 174 | args=(--minify) 175 | else 176 | args=(--watch) 177 | fi 178 | 179 | mkdir -p ../public/css 180 | DEBUG=0 tailwindcss -i css/app.css -o ../public/css/app.css "${args[@]}" 181 | } 182 | 183 | clean() { 184 | # Remove cache and other machine generates files 185 | rm -rf public/*.* public/admin public/js public/css public/images public/fonts \ 186 | public_collected/*.* public_collected/admin public_collected/js \ 187 | public_collected/css public_collected/images public_collected/fonts \ 188 | .ruff_cache/ .pytest_cache/ .coverage celerybeat-schedule 189 | 190 | touch public/.keep public_collected/.keep 191 | } 192 | 193 | ci:install-deps() { 194 | # Install Continuous Integration (CI) dependencies 195 | sudo apt-get install -y curl 196 | sudo curl \ 197 | -L https://raw.githubusercontent.com/nickjj/wait-until/v0.2.0/wait-until \ 198 | -o /usr/local/bin/wait-until && sudo chmod +x /usr/local/bin/wait-until 199 | } 200 | 201 | ci:test() { 202 | # Execute Continuous Integration (CI) pipeline 203 | lint:dockerfile "${@}" 204 | lint:shell 205 | format:shell --diff 206 | 207 | cp --no-clobber .env.example .env 208 | 209 | docker compose build 210 | docker compose up -d 211 | 212 | # shellcheck disable=SC1091 213 | . .env 214 | wait-until "docker compose exec -T \ 215 | -e PGPASSWORD=${POSTGRES_PASSWORD} postgres \ 216 | psql -U ${POSTGRES_USER} ${POSTGRES_USER} -c 'SELECT 1'" 217 | 218 | docker compose logs 219 | 220 | lint "${@}" 221 | format --check --diff 222 | manage migrate 223 | manage test 224 | } 225 | 226 | help() { 227 | printf "%s [args]\n\nTasks:\n" "${0}" 228 | 229 | compgen -A function | grep -v "^_" | cat -n 230 | 231 | printf "\nExtended help:\n Each task has comments for general usage\n" 232 | } 233 | 234 | # This idea is heavily inspired by: https://github.com/adriancooney/Taskfile 235 | TIMEFORMAT=$'\nTask completed in %3lR' 236 | time "${@:-help}" 237 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/src/__init__.py -------------------------------------------------------------------------------- /src/config/__init__.py: -------------------------------------------------------------------------------- 1 | # This will make sure the app is always imported when Django starts so that 2 | # shared_task will use this app. This is taken from Celery's docs at: 3 | # https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html 4 | from config.celery import app as celery_app 5 | 6 | __all__ = ("celery_app",) 7 | -------------------------------------------------------------------------------- /src/config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for hello project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /src/config/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 6 | 7 | app = Celery("hello") 8 | app.config_from_object("django.conf:settings", namespace="CELERY") 9 | app.autodiscover_tasks() 10 | -------------------------------------------------------------------------------- /src/config/gunicorn.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import multiprocessing 4 | import os 5 | 6 | from distutils.util import strtobool 7 | 8 | bind = f"0.0.0.0:{os.getenv('PORT', '8000')}" 9 | accesslog = "-" 10 | access_log_format = ( 11 | "%(h)s %(l)s %(u)s %(t)s '%(r)s' %(s)s %(b)s '%(f)s' '%(a)s' in %(D)sµs" # noqa: E501 12 | ) 13 | 14 | workers = int(os.getenv("WEB_CONCURRENCY", multiprocessing.cpu_count() * 2)) 15 | threads = int(os.getenv("PYTHON_MAX_THREADS", 1)) 16 | 17 | reload = bool(strtobool(os.getenv("WEB_RELOAD", "false"))) 18 | 19 | timeout = int(os.getenv("WEB_TIMEOUT", 120)) 20 | -------------------------------------------------------------------------------- /src/config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for hello project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | import socket 15 | import sys 16 | from pathlib import Path 17 | 18 | from distutils.util import strtobool 19 | 20 | # Build paths inside the project like this: BASE_DIR / "subdir". 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = os.environ["SECRET_KEY"] 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = bool(strtobool(os.getenv("DEBUG", "false"))) 28 | 29 | TESTING = "test" in sys.argv 30 | 31 | # https://docs.djangoproject.com/en/5.2/ref/settings/#std:setting-ALLOWED_HOSTS 32 | allowed_hosts = os.getenv("ALLOWED_HOSTS", ".localhost,127.0.0.1,[::1]") 33 | ALLOWED_HOSTS = list(map(str.strip, allowed_hosts.split(","))) 34 | 35 | # Application definitions 36 | INSTALLED_APPS = [ 37 | "pages.apps.PagesConfig", 38 | "django.contrib.admin", 39 | "django.contrib.auth", 40 | "django.contrib.contenttypes", 41 | "django.contrib.sessions", 42 | "django.contrib.messages", 43 | "django.contrib.staticfiles", 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | "django.middleware.security.SecurityMiddleware", 48 | "whitenoise.middleware.WhiteNoiseMiddleware", 49 | "django.contrib.sessions.middleware.SessionMiddleware", 50 | "django.middleware.common.CommonMiddleware", 51 | "django.middleware.csrf.CsrfViewMiddleware", 52 | "django.contrib.auth.middleware.AuthenticationMiddleware", 53 | "django.contrib.messages.middleware.MessageMiddleware", 54 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 55 | ] 56 | 57 | if not TESTING: 58 | INSTALLED_APPS = [*INSTALLED_APPS, "debug_toolbar"] 59 | MIDDLEWARE = [ 60 | "debug_toolbar.middleware.DebugToolbarMiddleware", 61 | *MIDDLEWARE, 62 | ] 63 | 64 | ROOT_URLCONF = "config.urls" 65 | 66 | # Starting with Django 4.1+ we need to pick which template loaders to use 67 | # based on our environment since 4.1+ will cache templates by default. 68 | default_loaders = [ 69 | "django.template.loaders.filesystem.Loader", 70 | "django.template.loaders.app_directories.Loader", 71 | ] 72 | 73 | cached_loaders = [("django.template.loaders.cached.Loader", default_loaders)] 74 | 75 | TEMPLATES = [ 76 | { 77 | "BACKEND": "django.template.backends.django.DjangoTemplates", 78 | "DIRS": [os.path.join(BASE_DIR, "templates")], 79 | "OPTIONS": { 80 | "context_processors": [ 81 | "django.template.context_processors.debug", 82 | "django.template.context_processors.request", 83 | "django.contrib.auth.context_processors.auth", 84 | "django.contrib.messages.context_processors.messages", 85 | ], 86 | "loaders": default_loaders if DEBUG else cached_loaders, 87 | }, 88 | }, 89 | ] 90 | 91 | WSGI_APPLICATION = "config.wsgi.application" 92 | 93 | # Database 94 | # https://docs.djangoproject.com/en/5.2/ref/settings/#databases 95 | DATABASES = { 96 | "default": { 97 | "ENGINE": "django.db.backends.postgresql", 98 | "NAME": os.getenv("POSTGRES_DB", "hello"), 99 | "USER": os.getenv("POSTGRES_USER", "hello"), 100 | "PASSWORD": os.getenv("POSTGRES_PASSWORD", "password"), 101 | "HOST": os.getenv("POSTGRES_HOST", "postgres"), 102 | "PORT": os.getenv("POSTGRES_PORT", "5432"), 103 | } 104 | } 105 | 106 | # Default primary key field type 107 | # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field 108 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 109 | 110 | # Password validation 111 | # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators 112 | AUTH_PASSWORD_VALIDATORS = [ 113 | { 114 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa: E501 115 | }, 116 | { 117 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", # noqa: E501 118 | }, 119 | { 120 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", # noqa: E501 121 | }, 122 | { 123 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", # noqa: E501 124 | }, 125 | ] 126 | 127 | # Sessions 128 | # https://docs.djangoproject.com/en/5.2/ref/settings/#sessions 129 | SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" 130 | 131 | # Redis 132 | REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379/0") 133 | 134 | # Caching 135 | # https://docs.djangoproject.com/en/5.2/topics/cache/ 136 | CACHES = { 137 | "default": { 138 | "BACKEND": "django.core.cache.backends.redis.RedisCache", 139 | "LOCATION": REDIS_URL, 140 | } 141 | } 142 | 143 | # Celery 144 | # https://docs.celeryproject.org/en/stable/userguide/configuration.html 145 | CELERY_BROKER_URL = REDIS_URL 146 | CELERY_RESULT_BACKEND = REDIS_URL 147 | 148 | # Internationalization 149 | # https://docs.djangoproject.com/en/5.2/topics/i18n/ 150 | LANGUAGE_CODE = "en-us" 151 | TIME_ZONE = "UTC" 152 | USE_I18N = True 153 | USE_L10N = True 154 | USE_TZ = True 155 | 156 | # Static files (CSS, JavaScript, Images) 157 | # https://docs.djangoproject.com/en/5.2/howto/static-files/ 158 | STATIC_URL = "/static/" 159 | STATICFILES_DIRS = ["/public", os.path.join(BASE_DIR, "..", "public")] 160 | STATIC_ROOT = "/public_collected" 161 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" 162 | 163 | # Django Debug Toolbar 164 | # https://django-debug-toolbar.readthedocs.io/ 165 | if DEBUG: 166 | # We need to configure an IP address to allow connections from, but in 167 | # Docker we can't use 127.0.0.1 since this runs in a container but we want 168 | # to access the toolbar from our browser outside of the container. 169 | hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) 170 | INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [ 171 | "127.0.0.1", 172 | "10.0.2.2", 173 | ] 174 | -------------------------------------------------------------------------------- /src/config/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for hello project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.conf import settings 19 | from django.contrib import admin 20 | from django.urls import include, path 21 | 22 | urlpatterns = [ 23 | path("up/", include("up.urls")), 24 | path("", include("pages.urls")), 25 | path("admin/", admin.site.urls), 26 | ] 27 | if not settings.TESTING: 28 | urlpatterns = [ 29 | *urlpatterns, 30 | path("__debug__/", include("debug_toolbar.urls")), 31 | ] 32 | -------------------------------------------------------------------------------- /src/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for hello project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | 4 | import os 5 | import sys 6 | 7 | 8 | def main(): 9 | """Run administrative tasks.""" 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /src/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/src/pages/__init__.py -------------------------------------------------------------------------------- /src/pages/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PagesConfig(AppConfig): 5 | name = "pages" 6 | -------------------------------------------------------------------------------- /src/pages/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/index.html" %} 2 | {% load static %} 3 | 4 | {% block title %}Docker + Django{% endblock %} 5 | 6 | {% block body %} 7 |
8 | Django logo 10 |
11 | 12 |

13 | 🐳 Learn the Docker fundamentals at: 14 | 15 | https://diveintodocker.com 16 | 17 |

18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /src/pages/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | class ViewTests(TestCase): 5 | def test_home_page(self): 6 | """Home page should respond with a success 200.""" 7 | response = self.client.get("/", follow=True) 8 | self.assertEqual(response.status_code, 200) 9 | -------------------------------------------------------------------------------- /src/pages/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from pages import views 4 | 5 | urlpatterns = [ 6 | path("", views.home, name="home"), 7 | ] 8 | -------------------------------------------------------------------------------- /src/pages/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django import get_version 4 | from django.conf import settings 5 | from django.shortcuts import render 6 | 7 | 8 | def home(request): 9 | context = { 10 | "debug": settings.DEBUG, 11 | "django_ver": get_version(), 12 | "python_ver": os.environ["PYTHON_VERSION"], 13 | } 14 | 15 | return render(request, "pages/home.html", context) 16 | -------------------------------------------------------------------------------- /src/templates/layouts/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | {% block title %}{% endblock %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {# Generated with: https://realfavicongenerator.net/ #} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 |

34 | Django {{ django_ver }} and Python {{ python_ver }} 35 |

36 |
37 |
38 |
39 | 40 | DEBUG = {{ debug }} 41 | 42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 | {% block body %}{% endblock %} 50 |
51 | 52 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/up/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickjj/docker-django-example/bbe3486c104717680c7f2e2f1280f9db8a706078/src/up/__init__.py -------------------------------------------------------------------------------- /src/up/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UpConfig(AppConfig): 5 | name = "up" 6 | -------------------------------------------------------------------------------- /src/up/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | class ViewTests(TestCase): 5 | def test_up(self): 6 | """Up should respond with a success 200.""" 7 | response = self.client.get("/up/", follow=True) 8 | self.assertEqual(response.status_code, 200) 9 | 10 | def test_up_databases(self): 11 | """Up databases should respond with a success 200.""" 12 | response = self.client.get("/up/databases", follow=True) 13 | self.assertEqual(response.status_code, 200) 14 | -------------------------------------------------------------------------------- /src/up/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from up import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="index"), 7 | path("databases", views.databases, name="databases"), 8 | ] 9 | -------------------------------------------------------------------------------- /src/up/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import connection 3 | from django.http import HttpResponse 4 | from redis import Redis 5 | 6 | redis = Redis.from_url(settings.REDIS_URL) 7 | 8 | 9 | def index(request): 10 | return HttpResponse("") 11 | 12 | 13 | def databases(request): 14 | redis.ping() 15 | connection.ensure_connection() 16 | 17 | return HttpResponse("") 18 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.13" 4 | 5 | [[package]] 6 | name = "amqp" 7 | version = "5.3.1" 8 | source = { registry = "https://pypi.org/simple" } 9 | dependencies = [ 10 | { name = "vine" }, 11 | ] 12 | sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013 } 13 | wheels = [ 14 | { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944 }, 15 | ] 16 | 17 | [[package]] 18 | name = "asgiref" 19 | version = "3.8.1" 20 | source = { registry = "https://pypi.org/simple" } 21 | sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 } 22 | wheels = [ 23 | { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, 24 | ] 25 | 26 | [[package]] 27 | name = "billiard" 28 | version = "4.2.1" 29 | source = { registry = "https://pypi.org/simple" } 30 | sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031 } 31 | wheels = [ 32 | { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766 }, 33 | ] 34 | 35 | [[package]] 36 | name = "celery" 37 | version = "5.5.3" 38 | source = { registry = "https://pypi.org/simple" } 39 | dependencies = [ 40 | { name = "billiard" }, 41 | { name = "click" }, 42 | { name = "click-didyoumean" }, 43 | { name = "click-plugins" }, 44 | { name = "click-repl" }, 45 | { name = "kombu" }, 46 | { name = "python-dateutil" }, 47 | { name = "vine" }, 48 | ] 49 | sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144 } 50 | wheels = [ 51 | { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775 }, 52 | ] 53 | 54 | [[package]] 55 | name = "click" 56 | version = "8.1.8" 57 | source = { registry = "https://pypi.org/simple" } 58 | dependencies = [ 59 | { name = "colorama", marker = "sys_platform == 'win32'" }, 60 | ] 61 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 62 | wheels = [ 63 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 64 | ] 65 | 66 | [[package]] 67 | name = "click-didyoumean" 68 | version = "0.3.1" 69 | source = { registry = "https://pypi.org/simple" } 70 | dependencies = [ 71 | { name = "click" }, 72 | ] 73 | sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089 } 74 | wheels = [ 75 | { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631 }, 76 | ] 77 | 78 | [[package]] 79 | name = "click-plugins" 80 | version = "1.1.1" 81 | source = { registry = "https://pypi.org/simple" } 82 | dependencies = [ 83 | { name = "click" }, 84 | ] 85 | sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164 } 86 | wheels = [ 87 | { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497 }, 88 | ] 89 | 90 | [[package]] 91 | name = "click-repl" 92 | version = "0.3.0" 93 | source = { registry = "https://pypi.org/simple" } 94 | dependencies = [ 95 | { name = "click" }, 96 | { name = "prompt-toolkit" }, 97 | ] 98 | sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449 } 99 | wheels = [ 100 | { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, 101 | ] 102 | 103 | [[package]] 104 | name = "colorama" 105 | version = "0.4.6" 106 | source = { registry = "https://pypi.org/simple" } 107 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 108 | wheels = [ 109 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 110 | ] 111 | 112 | [[package]] 113 | name = "django" 114 | version = "5.2.1" 115 | source = { registry = "https://pypi.org/simple" } 116 | dependencies = [ 117 | { name = "asgiref" }, 118 | { name = "sqlparse" }, 119 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 120 | ] 121 | sdist = { url = "https://files.pythonhosted.org/packages/ac/10/0d546258772b8f31398e67c85e52c66ebc2b13a647193c3eef8ee433f1a8/django-5.2.1.tar.gz", hash = "sha256:57fe1f1b59462caed092c80b3dd324fd92161b620d59a9ba9181c34746c97284", size = 10818735 } 122 | wheels = [ 123 | { url = "https://files.pythonhosted.org/packages/90/92/7448697b5838b3a1c6e1d2d6a673e908d0398e84dc4f803a2ce11e7ffc0f/django-5.2.1-py3-none-any.whl", hash = "sha256:a9b680e84f9a0e71da83e399f1e922e1ab37b2173ced046b541c72e1589a5961", size = 8301833 }, 124 | ] 125 | 126 | [[package]] 127 | name = "django-debug-toolbar" 128 | version = "5.2.0" 129 | source = { registry = "https://pypi.org/simple" } 130 | dependencies = [ 131 | { name = "django" }, 132 | { name = "sqlparse" }, 133 | ] 134 | sdist = { url = "https://files.pythonhosted.org/packages/2a/9f/97ba2648f66fa208fc7f19d6895586d08bc5f0ab930a1f41032e60f31a41/django_debug_toolbar-5.2.0.tar.gz", hash = "sha256:9e7f0145e1a1b7d78fcc3b53798686170a5b472d9cf085d88121ff823e900821", size = 297901 } 135 | wheels = [ 136 | { url = "https://files.pythonhosted.org/packages/fa/c2/ed3cb815002664349e9e50799b8c00ef15941f4cad797247cadbdeebab02/django_debug_toolbar-5.2.0-py3-none-any.whl", hash = "sha256:15627f4c2836a9099d795e271e38e8cf5204ccd79d5dbcd748f8a6c284dcd195", size = 262834 }, 137 | ] 138 | 139 | [[package]] 140 | name = "gunicorn" 141 | version = "23.0.0" 142 | source = { registry = "https://pypi.org/simple" } 143 | dependencies = [ 144 | { name = "packaging" }, 145 | ] 146 | sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } 147 | wheels = [ 148 | { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, 149 | ] 150 | 151 | [[package]] 152 | name = "hello" 153 | version = "0.1.0" 154 | source = { virtual = "." } 155 | dependencies = [ 156 | { name = "celery" }, 157 | { name = "django" }, 158 | { name = "django-debug-toolbar" }, 159 | { name = "gunicorn" }, 160 | { name = "psycopg" }, 161 | { name = "redis" }, 162 | { name = "ruff" }, 163 | { name = "setuptools" }, 164 | { name = "whitenoise" }, 165 | ] 166 | 167 | [package.metadata] 168 | requires-dist = [ 169 | { name = "celery", specifier = "==5.5.3" }, 170 | { name = "django", specifier = "==5.2.1" }, 171 | { name = "django-debug-toolbar", specifier = "==5.2.0" }, 172 | { name = "gunicorn", specifier = "==23.0.0" }, 173 | { name = "psycopg", specifier = "==3.2.9" }, 174 | { name = "redis", specifier = "==6.2.0" }, 175 | { name = "ruff", specifier = "==0.11.12" }, 176 | { name = "setuptools", specifier = "==80.9.0" }, 177 | { name = "whitenoise", specifier = "==6.9.0" }, 178 | ] 179 | 180 | [[package]] 181 | name = "kombu" 182 | version = "5.5.2" 183 | source = { registry = "https://pypi.org/simple" } 184 | dependencies = [ 185 | { name = "amqp" }, 186 | { name = "tzdata" }, 187 | { name = "vine" }, 188 | ] 189 | sdist = { url = "https://files.pythonhosted.org/packages/c8/12/7a340f48920f30d6febb65d0c4aca70ed01b29e116131152977df78a9a39/kombu-5.5.2.tar.gz", hash = "sha256:2dd27ec84fd843a4e0a7187424313f87514b344812cb98c25daddafbb6a7ff0e", size = 461522 } 190 | wheels = [ 191 | { url = "https://files.pythonhosted.org/packages/af/ba/939f3db0fca87715c883e42cc93045347d61a9d519c270a38e54a06db6e1/kombu-5.5.2-py3-none-any.whl", hash = "sha256:40f3674ed19603b8a771b6c74de126dbf8879755a0337caac6602faa82d539cd", size = 209763 }, 192 | ] 193 | 194 | [[package]] 195 | name = "packaging" 196 | version = "24.2" 197 | source = { registry = "https://pypi.org/simple" } 198 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 199 | wheels = [ 200 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 201 | ] 202 | 203 | [[package]] 204 | name = "prompt-toolkit" 205 | version = "3.0.50" 206 | source = { registry = "https://pypi.org/simple" } 207 | dependencies = [ 208 | { name = "wcwidth" }, 209 | ] 210 | sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } 211 | wheels = [ 212 | { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, 213 | ] 214 | 215 | [[package]] 216 | name = "psycopg" 217 | version = "3.2.9" 218 | source = { registry = "https://pypi.org/simple" } 219 | dependencies = [ 220 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 221 | ] 222 | sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122 } 223 | wheels = [ 224 | { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705 }, 225 | ] 226 | 227 | [[package]] 228 | name = "python-dateutil" 229 | version = "2.9.0.post0" 230 | source = { registry = "https://pypi.org/simple" } 231 | dependencies = [ 232 | { name = "six" }, 233 | ] 234 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 235 | wheels = [ 236 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, 237 | ] 238 | 239 | [[package]] 240 | name = "redis" 241 | version = "6.2.0" 242 | source = { registry = "https://pypi.org/simple" } 243 | sdist = { url = "https://files.pythonhosted.org/packages/ea/9a/0551e01ba52b944f97480721656578c8a7c46b51b99d66814f85fe3a4f3e/redis-6.2.0.tar.gz", hash = "sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977", size = 4639129 } 244 | wheels = [ 245 | { url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659 }, 246 | ] 247 | 248 | [[package]] 249 | name = "ruff" 250 | version = "0.11.12" 251 | source = { registry = "https://pypi.org/simple" } 252 | sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289 } 253 | wheels = [ 254 | { url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597 }, 255 | { url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154 }, 256 | { url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048 }, 257 | { url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062 }, 258 | { url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152 }, 259 | { url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067 }, 260 | { url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807 }, 261 | { url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261 }, 262 | { url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601 }, 263 | { url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186 }, 264 | { url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032 }, 265 | { url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370 }, 266 | { url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529 }, 267 | { url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642 }, 268 | { url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511 }, 269 | { url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573 }, 270 | { url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770 }, 271 | ] 272 | 273 | [[package]] 274 | name = "setuptools" 275 | version = "80.9.0" 276 | source = { registry = "https://pypi.org/simple" } 277 | sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } 278 | wheels = [ 279 | { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, 280 | ] 281 | 282 | [[package]] 283 | name = "six" 284 | version = "1.17.0" 285 | source = { registry = "https://pypi.org/simple" } 286 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 287 | wheels = [ 288 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, 289 | ] 290 | 291 | [[package]] 292 | name = "sqlparse" 293 | version = "0.5.3" 294 | source = { registry = "https://pypi.org/simple" } 295 | sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 } 296 | wheels = [ 297 | { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, 298 | ] 299 | 300 | [[package]] 301 | name = "tzdata" 302 | version = "2025.2" 303 | source = { registry = "https://pypi.org/simple" } 304 | sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } 305 | wheels = [ 306 | { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, 307 | ] 308 | 309 | [[package]] 310 | name = "vine" 311 | version = "5.1.0" 312 | source = { registry = "https://pypi.org/simple" } 313 | sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980 } 314 | wheels = [ 315 | { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636 }, 316 | ] 317 | 318 | [[package]] 319 | name = "wcwidth" 320 | version = "0.2.13" 321 | source = { registry = "https://pypi.org/simple" } 322 | sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } 323 | wheels = [ 324 | { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, 325 | ] 326 | 327 | [[package]] 328 | name = "whitenoise" 329 | version = "6.9.0" 330 | source = { registry = "https://pypi.org/simple" } 331 | sdist = { url = "https://files.pythonhosted.org/packages/b9/cf/c15c2f21aee6b22a9f6fc9be3f7e477e2442ec22848273db7f4eb73d6162/whitenoise-6.9.0.tar.gz", hash = "sha256:8c4a7c9d384694990c26f3047e118c691557481d624f069b7f7752a2f735d609", size = 25920 } 332 | wheels = [ 333 | { url = "https://files.pythonhosted.org/packages/64/b2/2ce9263149fbde9701d352bda24ea1362c154e196d2fda2201f18fc585d7/whitenoise-6.9.0-py3-none-any.whl", hash = "sha256:c8a489049b7ee9889617bb4c274a153f3d979e8f51d2efd0f5b403caf41c57df", size = 20161 }, 334 | ] 335 | --------------------------------------------------------------------------------