├── .gitignore ├── LICENSE ├── README.md ├── django_dockerizer ├── __init__.py ├── contents.py ├── dockerizer.py └── utils.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | # ruff 171 | .ruff_cache/ 172 | 173 | # LSP config files 174 | pyrightconfig.json 175 | 176 | # End of https://www.toptal.com/developers/gitignore/api/python -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Isa Arifoglu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/pypi/pyversions/django-colorfield.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/) 2 | [![](https://img.shields.io/pypi/djversions/django-colorfield?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/) 3 | 4 | [![](https://img.shields.io/pypi/v/django-dockerizer.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-dockerizer/) 5 | [![Downloads](https://static.pepy.tech/badge/django-dockerizer)](https://pepy.tech/project/django-dockerizer) 6 | [![](https://img.shields.io/github/stars/arifogluisa/django-dockerizer?logo=github)](https://github.com/arifogluisa/django-dockerizer/stargazers) 7 | [![](https://img.shields.io/pypi/l/django-dockerizer.svg?color=blue)](https://github.com/arifogluisa/django-dockerizer/blob/master/LICENSE) 8 | 9 | [![](https://img.shields.io/codacy/grade/194566618f424a819ce43450ea0af081?logo=codacy)](https://app.codacy.com/gh/arifogluisa/django-dockerizer) 10 | [![](https://img.shields.io/codeclimate/maintainability/arifogluisa/django-dockerizer?logo=code-climate)](https://codeclimate.com/github/arifogluisa/django-dockerizer/) 11 | [![](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 12 | 13 | # django-dockerizer 14 | A package used to dockerize and make ready Django projects to deploy. 15 | 16 | 17 | ## Installation 18 | - Run `pip install django-dockerizer` 19 | 20 | 21 | ## Usage 22 | 23 | ### Without celery 24 | - Run `dockerize` command in project's base directory (in the same directory with manage.py file) 25 | 26 | ### With celery 27 | - Run `dockerize --celery` command in project's base directory (in the same directory with manage.py file) 28 | 29 | ### Requirements 30 | - It creates `requirements.txt` files in `bin/dev` and `bin/prod` folders. And all packages which installed in virtual environment(if there is an activated one) or in the system, will be written in `requirements.txt` files. Just be sure there are Django, psycopg2-binary, celery, django_celery_results, django_celery_beat in INSTALLED_APPS in settings.py 31 | 32 | ### Database 33 | - It configures docker-compose for Postgresql database, so be sure your DATABASE configuration in settings.py is okay for Postgresql database with credentials in .env file 34 | 35 | ## Credits 36 | Originally developed by [Isa Arifoglu](https://github.com/arifogluisa) 37 | 38 | 39 | ## License 40 | Released under [MIT License](LICENSE). 41 | 42 | 43 | ## Supporting 44 | 45 | - :star: Star this project on [GitHub](https://github.com/arifogluisa/django-dockerizer) 46 | - :octocat: Follow me on [GitHub](https://github.com/arifogluisa) 47 | - :blue_heart: Follow me on [Twitter](https://twitter.com/arifogluisa) 48 | -------------------------------------------------------------------------------- /django_dockerizer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arifogluisa/django-dockerizer/c3e3ed0e3155686a00b7c17f2a175f418d49f8bf/django_dockerizer/__init__.py -------------------------------------------------------------------------------- /django_dockerizer/contents.py: -------------------------------------------------------------------------------- 1 | from .utils import PROJECT_NAME, generate_or_retrieve_passwords 2 | 3 | # Retrieve or generate passwords 4 | DB_PASS, REDIS_PASS = generate_or_retrieve_passwords() 5 | 6 | ENV_TYPES = ("dev", "prod") 7 | 8 | DOCKERFILE_DEV = """FROM python:3.10 9 | ENV PYTHONUNBUFFERED 1 10 | ENV DEBUG True 11 | COPY requirements.txt /code/requirements.txt 12 | WORKDIR /code 13 | RUN pip install -r requirements.txt 14 | ADD . . 15 | """ 16 | 17 | DOCKERFILE_PROD = """FROM python:3.10-slim 18 | 19 | ENV PYTHONUNBUFFERED 1 20 | ENV DEBUG False 21 | ENV APP_ROOT /code 22 | 23 | WORKDIR ${APP_ROOT} 24 | 25 | COPY ./requirements.txt requirements.txt 26 | 27 | RUN apt-get update && \\ 28 | apt-get install -y \\ 29 | locales \\ 30 | locales-all \\ 31 | build-essential \\ 32 | libpcre3 \\ 33 | libpcre3-dev \\ 34 | curl \\ 35 | libzbar-dev \\ 36 | && pip install --upgrade pip \\ 37 | && pip install --no-cache-dir -r requirements.txt \\ 38 | && apt-get clean --dry-run 39 | 40 | COPY ./mime.types /etc/mime.types 41 | COPY ./uwsgi.ini /conf/uwsgi.ini 42 | COPY ../.. /code 43 | 44 | # Start uWSGI 45 | CMD [ "uwsgi", "--ini", "/conf/uwsgi.ini"] 46 | """ 47 | 48 | UWSGI = f"""[uwsgi] 49 | env = DJANGO_SETTINGS_MODULE={PROJECT_NAME}.settings 50 | env = UWSGI_VIRTUALENV=/venv 51 | env = IS_WSGI=True 52 | env = LANG=en_US.UTF-8 53 | workdir = /code 54 | chdir = /code 55 | logformat=%(ltime) "%(method) %(uri) %(proto)" status=%(status) res-time=%(msecs)ms 56 | module = {PROJECT_NAME}.wsgi:application 57 | enable-threads = true 58 | master = True 59 | pidfile = /tmp/app-master.pid 60 | vacuum = True 61 | max-requests = 5000 62 | processes = 5 63 | cheaper = 2 64 | cheaper-initial = 5 65 | gid = root 66 | uid = root 67 | http-socket = 0.0.0.0:$(HTTP_PORT) 68 | stats = 0.0.0.0:$(STATS_PORT) 69 | harakiri = $(TIMEOUT) 70 | print = Your timeout is %(harakiri) 71 | static-map = /static=%(workdir)/static 72 | static-map = /media=%(workdir)/media 73 | """ 74 | 75 | MIME_TYPES = """# mime type definition extracted from nginx 76 | # https://github.com/nginx/nginx/blob/master/conf/mime.types 77 | 78 | text/html html htm shtml 79 | text/css css 80 | text/xml xml 81 | image/gif gif 82 | image/jpeg jpeg jpg 83 | application/javascript js 84 | application/atom+xml atom 85 | application/rss+xml rss 86 | 87 | text/mathml mml 88 | text/plain txt 89 | text/vnd.sun.j2me.app-descriptor jad 90 | text/vnd.wap.wml wml 91 | text/x-component htc 92 | 93 | image/png png 94 | image/tiff tif tiff 95 | image/vnd.wap.wbmp wbmp 96 | image/x-icon ico 97 | image/x-jng jng 98 | image/x-ms-bmp bmp 99 | image/svg+xml svg svgz 100 | image/webp webp 101 | 102 | application/font-woff woff 103 | application/java-archive jar war ear 104 | application/json json 105 | application/mac-binhex40 hqx 106 | application/msword doc 107 | application/pdf pdf 108 | application/postscript ps eps ai 109 | application/rtf rtf 110 | application/vnd.apple.mpegurl m3u8 111 | application/vnd.ms-excel xls 112 | application/vnd.ms-fontobject eot 113 | application/vnd.ms-powerpoint ppt 114 | application/vnd.wap.wmlc wmlc 115 | application/vnd.google-earth.kml+xml kml 116 | application/vnd.google-earth.kmz kmz 117 | application/x-7z-compressed 7z 118 | application/x-cocoa cco 119 | application/x-java-archive-diff jardiff 120 | application/x-java-jnlp-file jnlp 121 | application/x-makeself run 122 | application/x-perl pl pm 123 | application/x-pilot prc pdb 124 | application/x-rar-compressed rar 125 | application/x-redhat-package-manager rpm 126 | application/x-sea sea 127 | application/x-shockwave-flash swf 128 | application/x-stuffit sit 129 | application/x-tcl tcl tk 130 | application/x-x509-ca-cert der pem crt 131 | application/x-xpinstall xpi 132 | application/xhtml+xml xhtml 133 | application/xspf+xml xspf 134 | application/zip zip 135 | 136 | application/octet-stream bin exe dll 137 | application/octet-stream deb 138 | application/octet-stream dmg 139 | application/octet-stream iso img 140 | application/octet-stream msi msp msm 141 | 142 | application/vnd.openxmlformats-officedocument.wordprocessingml.document docx 143 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx 144 | application/vnd.openxmlformats-officedocument.presentationml.presentation pptx 145 | 146 | audio/midi mid midi kar 147 | audio/mpeg mp3 148 | audio/ogg ogg 149 | audio/x-m4a m4a 150 | audio/x-realaudio ra 151 | 152 | video/3gpp 3gpp 3gp 153 | video/mp2t ts 154 | video/mp4 mp4 155 | video/mpeg mpeg mpg 156 | video/quicktime mov 157 | video/webm webm 158 | video/x-flv flv 159 | video/x-m4v m4v 160 | video/x-mng mng 161 | video/x-ms-asf asx asf 162 | video/x-ms-wmv wmv 163 | video/x-msvideo avi 164 | """ 165 | 166 | DOCKER_COMPOSE_DEV = f"""version: '3' 167 | 168 | services: 169 | postgres: 170 | container_name: postgres-db-{PROJECT_NAME} 171 | image: postgres:13.0-alpine 172 | ports: 173 | - 5432:5432 174 | volumes: 175 | - {PROJECT_NAME}_postgres-data:/var/lib/postgresql/data 176 | env_file: .env 177 | 178 | web: 179 | container_name: {PROJECT_NAME} 180 | build: . 181 | restart: "always" 182 | env_file: ./.env 183 | volumes: 184 | - ../../:/code 185 | ports: 186 | - "8000:8000" 187 | depends_on: 188 | - "postgres" 189 | command: bash -c " python /code/manage.py makemigrations --noinput && python /code/manage.py migrate && python /code/manage.py runserver 0.0.0.0:8000" 190 | 191 | volumes: 192 | {PROJECT_NAME}_postgres-data: 193 | """ 194 | 195 | DOCKER_COMPOSE_WITH_CELERY_DEV = f"""version: '3' 196 | 197 | services: 198 | postgres: 199 | container_name: postgres-db-{PROJECT_NAME} 200 | image: postgres:13.0-alpine 201 | ports: 202 | - 5432:5432 203 | volumes: 204 | - {PROJECT_NAME}_postgres-data:/var/lib/postgresql/data 205 | env_file: .env 206 | 207 | redis: 208 | container_name: redis 209 | image: redis:5 210 | restart: "on-failure" 211 | expose: 212 | - '6379' 213 | ports: 214 | - '6379:6379' 215 | volumes: 216 | - {PROJECT_NAME}_redis-data:/data 217 | 218 | celery: &celery 219 | container_name: celery 220 | build: . 221 | env_file: .env 222 | volumes: 223 | - ../..:/code 224 | command: bash -c "cd /code/ && celery --app={PROJECT_NAME}.celery:app worker -B --loglevel=INFO" 225 | depends_on: 226 | - web 227 | - redis 228 | links: 229 | - postgres 230 | - redis 231 | 232 | web: 233 | container_name: {PROJECT_NAME} 234 | build: . 235 | restart: "always" 236 | env_file: ./.env 237 | volumes: 238 | - ../../:/code 239 | ports: 240 | - "8000:8000" 241 | depends_on: 242 | - "postgres" 243 | command: bash -c " python /code/manage.py makemigrations --noinput && python /code/manage.py migrate && python /code/manage.py runserver 0.0.0.0:8000" 244 | 245 | volumes: 246 | {PROJECT_NAME}_redis-data: 247 | {PROJECT_NAME}_postgres-data: 248 | """ 249 | 250 | DOCKER_COMPOSE_PROD = """version: '3' 251 | 252 | services: 253 | 254 | nginx-proxy: 255 | image: jwilder/nginx-proxy 256 | container_name: nginx-proxy 257 | restart: "always" 258 | ports: 259 | - "80:80" 260 | volumes: 261 | - /var/run/docker.sock:/tmp/docker.sock:ro 262 | - ../nginx.conf:/etc/nginx/nginx.conf 263 | - ../../static:/app/static 264 | - ../../media:/app/media 265 | depends_on: 266 | - "app" 267 | 268 | app: 269 | container_name: app 270 | build: . 271 | restart: "always" 272 | env_file: .env 273 | environment: 274 | - VIRTUAL_HOST=66.666.666.666 # this is example, replace this with your server IP 275 | - VIRTUAL_PORT=8000 276 | - HTTP_PORT=8000 277 | - STATS_PORT=8001 278 | volumes: 279 | - ../..:/code 280 | ports: 281 | - "8015:8000" 282 | links: 283 | - postgres 284 | depends_on: 285 | - "postgres" 286 | 287 | postgres: 288 | container_name: postgres-db 289 | image: postgres:13 290 | ports: 291 | - "5432:5432" 292 | volumes: 293 | - ./pgdb:/var/lib/postgresql/data 294 | env_file: .env 295 | 296 | networks: 297 | default: 298 | external: 299 | name: nginx-proxy 300 | """ 301 | 302 | DOCKER_COMPOSE_WITH_CELERY_PROD = f"""version: '3' 303 | 304 | services: 305 | 306 | nginx-proxy: 307 | image: jwilder/nginx-proxy 308 | container_name: nginx-proxy 309 | restart: "always" 310 | ports: 311 | - "80:80" 312 | volumes: 313 | - /var/run/docker.sock:/tmp/docker.sock:ro 314 | - ../nginx.conf:/etc/nginx/nginx.conf 315 | - ../../static:/app/static 316 | - ../../media:/app/media 317 | depends_on: 318 | - "app" 319 | 320 | app: 321 | container_name: app 322 | build: . 323 | restart: "always" 324 | env_file: .env 325 | environment: 326 | - VIRTUAL_HOST=66.666.666.666 # this is example, replace this with your server IP 327 | - VIRTUAL_PORT=8000 328 | - HTTP_PORT=8000 329 | - STATS_PORT=8001 330 | volumes: 331 | - ../..:/code 332 | ports: 333 | - "8015:8000" 334 | links: 335 | - postgres 336 | depends_on: 337 | - "postgres" 338 | 339 | postgres: 340 | container_name: postgres-db 341 | image: postgres:13 342 | ports: 343 | - "5432:5432" 344 | volumes: 345 | - ./pgdb:/var/lib/postgresql/data 346 | env_file: .env 347 | 348 | redis: 349 | build: 350 | context: . 351 | dockerfile: redis.dockerfile 352 | image: redis:4.0.11 353 | restart: "on-failure" 354 | container_name: redis 355 | ports: 356 | - "6379:6379" 357 | volumes: 358 | - ./redisdb:/var/lib/redis 359 | env_file: .env 360 | 361 | celery: 362 | restart: "always" 363 | build: . 364 | container_name: celery 365 | env_file: .env 366 | command: celery --app={PROJECT_NAME}.celery:app worker -B --loglevel=INFO 367 | volumes: 368 | - ../..:/code 369 | links: 370 | - redis 371 | - postgres 372 | depends_on: 373 | - "redis" 374 | - "postgres" 375 | 376 | networks: 377 | default: 378 | external: 379 | name: nginx-proxy 380 | """ 381 | 382 | REDIS_DOCKERFILE = """FROM redis:4.0.11 383 | 384 | CMD ["sh", "-c", "exec redis-server --requirepass \"$REDIS_PASSWORD\""] 385 | """ 386 | 387 | NGINX_CONF = """user nginx; 388 | worker_processes auto; 389 | error_log /var/log/nginx/error.log warn; 390 | pid /var/run/nginx.pid; 391 | events { 392 | worker_connections 1024; 393 | } 394 | http { 395 | include /etc/nginx/mime.types; 396 | default_type application/octet-stream; 397 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 398 | '$status $body_bytes_sent "$http_referer" ' 399 | '"$http_user_agent" "$http_x_forwarded_for"'; 400 | access_log /var/log/nginx/access.log main; 401 | sendfile on; 402 | keepalive_timeout 65; 403 | include /etc/nginx/conf.d/*.conf; 404 | # aditional 405 | client_max_body_size 200M; 406 | } 407 | """ 408 | 409 | DEV_ENV = f"""# PostgreSQL 410 | POSTGRES_DB={PROJECT_NAME}_db 411 | POSTGRES_USER={PROJECT_NAME}_user 412 | POSTGRES_PASSWORD={DB_PASS} 413 | POSTGRES_HOST=postgres 414 | POSTGRES_PORT=5432 415 | LC_ALL=C.UTF-8 416 | 417 | DEBUG=True 418 | """ 419 | 420 | DEV_ENV_WITH_CELERY = f"""# PostgreSQL 421 | POSTGRES_DB={PROJECT_NAME}_db 422 | POSTGRES_USER={PROJECT_NAME}_user 423 | POSTGRES_PASSWORD={DB_PASS} 424 | POSTGRES_HOST=postgres 425 | POSTGRES_PORT=5432 426 | LC_ALL=C.UTF-8 427 | 428 | DEBUG=True 429 | 430 | # Redis 431 | REDIS_HOST=redis 432 | REDIS_PORT=6379 433 | REDIS_PASSWORD={REDIS_PASS} 434 | 435 | CELERY_BROKER=redis://redis:6379/0 436 | CELERY_BACKEND=redis://redis:6379/0 437 | """ 438 | 439 | PROD_ENV = f"""# PostgreSQL 440 | POSTGRES_DB={PROJECT_NAME}_db 441 | POSTGRES_USER={PROJECT_NAME}_user 442 | POSTGRES_PASSWORD={DB_PASS} 443 | POSTGRES_HOST=postgres 444 | POSTGRES_PORT=5432 445 | LC_ALL=C.UTF-8 446 | 447 | DEBUG=False 448 | """ 449 | 450 | PROD_ENV_WITH_CELERY = f"""# PostgreSQL 451 | POSTGRES_DB={PROJECT_NAME}_db 452 | POSTGRES_USER={PROJECT_NAME}_user 453 | POSTGRES_PASSWORD={DB_PASS} 454 | POSTGRES_HOST=postgres 455 | POSTGRES_PORT=5432 456 | LC_ALL=C.UTF-8 457 | 458 | DEBUG=False 459 | 460 | # Redis 461 | REDIS_HOST=redis 462 | REDIS_PORT=6379 463 | REDIS_PASSWORD={REDIS_PASS} 464 | 465 | CELERY_BROKER=redis://:{REDIS_PASS}@redis:6379/0 466 | CELERY_BROKER_URL=redis://:{REDIS_PASS}@redis:6379/0 467 | CELERY_BACKEND=redis://:{REDIS_PASS}@redis:6379/0 468 | """ 469 | 470 | CELERY = f"""from __future__ import absolute_import, unicode_literals 471 | 472 | import logging 473 | import os 474 | 475 | from django.conf import settings 476 | from celery import Celery 477 | 478 | logger = logging.getLogger("Celery") 479 | 480 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{PROJECT_NAME}.settings") 481 | 482 | app = Celery("{PROJECT_NAME}") 483 | 484 | app.config_from_object("django.conf:settings", namespace="CELERY") 485 | 486 | app.autodiscover_tasks() 487 | 488 | @app.task(bind=True) 489 | def debug_task(self): 490 | print("Request: {{self.request}}") 491 | 492 | if settings.DEBUG: 493 | app.conf.update( 494 | BROKER_URL='redis://localhost:6379/0', 495 | CELERYBEAT_SCHEDULER='django_celery_beat.schedulers:DatabaseScheduler', 496 | CELERY_RESULT_BACKEND='redis://localhost:6379/1', 497 | CELERY_DISABLE_RATE_LIMITS=True, 498 | CELERY_ACCEPT_CONTENT=['json', ], 499 | CELERY_TASK_SERIALIZER='json', 500 | CELERY_RESULT_SERIALIZER='json', 501 | # CELERY_TIMEZONE='Asia/Baku', 502 | ) 503 | else: 504 | app.conf.update( 505 | BROKER_URL='redis://:{REDIS_PASS}@redis:6379/0', 506 | CELERYBEAT_SCHEDULER='django_celery_beat.schedulers:DatabaseScheduler', 507 | CELERY_RESULT_BACKEND='redis://:{REDIS_PASS}@redis:6379/1', 508 | CELERY_DISABLE_RATE_LIMITS=True, 509 | CELERY_ACCEPT_CONTENT=['json', ], 510 | CELERY_TASK_SERIALIZER='json', 511 | CELERY_RESULT_SERIALIZER='json', 512 | # CELERY_TIMEZONE='Asia/Baku', 513 | ) 514 | """ 515 | 516 | SINGLE_FILES = ( 517 | ("uwsgi.ini", UWSGI), 518 | ("mime.types", MIME_TYPES), 519 | ) 520 | -------------------------------------------------------------------------------- /django_dockerizer/dockerizer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import subprocess 4 | 5 | from .contents import ( 6 | CELERY, 7 | DEV_ENV, 8 | DEV_ENV_WITH_CELERY, 9 | DOCKER_COMPOSE_DEV, 10 | DOCKER_COMPOSE_PROD, 11 | DOCKER_COMPOSE_WITH_CELERY_DEV, 12 | DOCKER_COMPOSE_WITH_CELERY_PROD, 13 | DOCKERFILE_DEV, 14 | DOCKERFILE_PROD, 15 | ENV_TYPES, 16 | NGINX_CONF, 17 | PROD_ENV, 18 | PROD_ENV_WITH_CELERY, 19 | REDIS_DOCKERFILE, 20 | SINGLE_FILES, 21 | ) 22 | from .utils import BASE_DIR, create_celery_file 23 | 24 | 25 | def parse_arguments(): 26 | parser = argparse.ArgumentParser(description="Django Dockerizer Tool") 27 | parser.add_argument( 28 | "--celery", help="Include Celery configurations", action="store_true" 29 | ) 30 | # parser.add_argument("--redis", help="Include Redis configurations", action="store_true") 31 | 32 | return parser.parse_args() 33 | 34 | 35 | args = parse_arguments() 36 | 37 | 38 | def create_directory_structure(): 39 | os.makedirs(os.path.join(BASE_DIR, "bin/dev"), exist_ok=True) 40 | os.makedirs(os.path.join(BASE_DIR, "bin/prod"), exist_ok=True) 41 | 42 | 43 | def generate_docker_files(env_type): 44 | content = DOCKERFILE_DEV if env_type == "dev" else DOCKERFILE_PROD 45 | with open(os.path.join(BASE_DIR, "bin", env_type, "Dockerfile"), "w") as file: 46 | file.write(content) 47 | if args.celery and env_type == "prod": 48 | with open( 49 | os.path.join(BASE_DIR, "bin", env_type, "redis.dockerfile"), "w" 50 | ) as file: 51 | file.write(REDIS_DOCKERFILE) 52 | 53 | 54 | def generate_single_files(): 55 | """ 56 | Generating uwsgi.ini, mime.types and nginx.conf files 57 | """ 58 | for file_name, content in SINGLE_FILES: 59 | with open(os.path.join(BASE_DIR, "bin", "prod", file_name), "w") as file: 60 | file.write(content) 61 | with open(os.path.join(BASE_DIR, "bin", "nginx.conf"), "w") as file: 62 | file.write(NGINX_CONF) 63 | 64 | 65 | def generate_env_files(env_type): 66 | if args.celery: 67 | content = DEV_ENV_WITH_CELERY if env_type == "dev" else PROD_ENV_WITH_CELERY 68 | else: 69 | content = DEV_ENV if env_type == "dev" else PROD_ENV 70 | with open(os.path.join(BASE_DIR, "bin", env_type, ".env"), "w") as file: 71 | file.write(content) 72 | 73 | 74 | def generate_docker_compose_files(env_type): 75 | if args.celery: 76 | content = ( 77 | DOCKER_COMPOSE_WITH_CELERY_DEV 78 | if env_type == "dev" 79 | else DOCKER_COMPOSE_WITH_CELERY_PROD 80 | ) 81 | else: 82 | content = DOCKER_COMPOSE_DEV if env_type == "dev" else DOCKER_COMPOSE_PROD 83 | with open( 84 | os.path.join(BASE_DIR, "bin", env_type, "docker-compose.yml"), "w" 85 | ) as file: 86 | file.write(content) 87 | 88 | 89 | def generate_requirements_files(env_type): 90 | req_path = os.path.join(BASE_DIR, "bin", env_type, "requirements.txt") 91 | with open(req_path, "w") as file: 92 | # Run pip freeze and capture the output 93 | pip_freeze_output = subprocess.check_output( 94 | ["pip", "freeze"], universal_newlines=True 95 | ) 96 | file.write(pip_freeze_output) 97 | 98 | # If it's the prod environment, add uwsgi 99 | if env_type == "prod": 100 | file.write("uWSGI\n") 101 | 102 | 103 | def dockerize(): 104 | create_directory_structure() 105 | generate_single_files() 106 | 107 | if args.celery: 108 | create_celery_file(CELERY) 109 | 110 | for env_type in ENV_TYPES: 111 | generate_docker_files(env_type) 112 | generate_docker_compose_files(env_type) 113 | generate_env_files(env_type) 114 | generate_requirements_files(env_type) 115 | 116 | 117 | if __name__ == "__main__": 118 | dockerize() 119 | -------------------------------------------------------------------------------- /django_dockerizer/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import string 4 | 5 | 6 | BASE_DIR = os.getcwd() 7 | 8 | 9 | def find_django_project_name(): 10 | for root, dirs, files in os.walk(BASE_DIR): 11 | if "settings.py" in files: 12 | settings_path = os.path.join(root, "settings.py") 13 | with open(settings_path, 'r') as file: 14 | contents = file.read() 15 | if 'django.contrib' in contents: 16 | return os.path.basename(root), root 17 | raise Exception("Could not find Django project name") 18 | 19 | 20 | PROJECT_NAME, SETTINGS_DIRECTORY = find_django_project_name() 21 | 22 | 23 | def generate_password(length=12): 24 | characters = string.ascii_lowercase + string.ascii_uppercase + string.digits 25 | return ''.join(random.choice(characters) for _ in range(length)) 26 | 27 | 28 | def get_env_variable(env_file, variable_name): 29 | with open(env_file, 'r') as file: 30 | for line in file: 31 | if line.startswith(variable_name): 32 | return line.split('=')[1].strip() 33 | return None 34 | 35 | 36 | def generate_or_retrieve_passwords(): 37 | env_file_path = os.path.join(BASE_DIR, 'bin/dev', '.env') 38 | 39 | # Check if .env file exists 40 | if os.path.exists(env_file_path): 41 | db_pass = get_env_variable(env_file_path, 'POSTGRES_PASSWORD') 42 | redis_pass = get_env_variable(env_file_path, 'REDIS_PASSWORD') 43 | # If for any reason the variables aren't in the .env file, generate new ones 44 | if not db_pass: 45 | db_pass = generate_password(65) 46 | if not redis_pass: 47 | redis_pass = generate_password(16) 48 | else: 49 | db_pass = generate_password(65) 50 | redis_pass = generate_password(16) 51 | 52 | return db_pass, redis_pass 53 | 54 | 55 | def create_celery_file(content): 56 | celery_path = os.path.join(SETTINGS_DIRECTORY, 'celery.py') 57 | 58 | if not os.path.exists(celery_path): 59 | with open(celery_path, 'w') as file: 60 | file.write(content) 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open("README.md", "r") as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name="django_dockerizer", 8 | version="0.3.0", 9 | description="Dockerize and make ready to deploy Django projects", 10 | packages=find_packages(), 11 | long_description=long_description, 12 | long_description_content_type="text/markdown", 13 | url="https://github.com/arifogluisa/django-dockerizer", 14 | author="Isa Arifoglu", 15 | author_email="arifogluisa@gmail.com", 16 | license="MIT", 17 | classifiers=[ 18 | "License :: OSI Approved :: MIT License", 19 | "Programming Language :: Python :: 3.8", 20 | "Operating System :: OS Independent", 21 | ], 22 | entry_points={ 23 | 'console_scripts': ['dockerize=django_dockerizer.dockerizer:dockerize'], 24 | }, 25 | python_requires=">=3.8", 26 | ) 27 | --------------------------------------------------------------------------------