{"id":18821006,"url":"https://github.com/memphis-tools/dockerize_django_app_on_digitalocean","last_synced_at":"2025-09-23T09:41:15.741Z","repository":{"id":195180733,"uuid":"692238937","full_name":"memphis-tools/dockerize_django_app_on_digitalocean","owner":"memphis-tools","description":"This a dummy working Django project to use for learning purposes.  Application is fully deployed on a vm DigitalOcean instance (not  \"app\"). The application is an exercise based on a local development required by a French Python's Developer curriculum (Openclassroom).","archived":false,"fork":false,"pushed_at":"2025-03-22T03:31:58.000Z","size":1805,"stargazers_count":1,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T14:21:56.433Z","etag":null,"topics":["digitalocean-droplets","django-project","docker","postgresql","python-3-10","terraform"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/memphis-tools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-15T22:17:22.000Z","updated_at":"2025-01-18T17:02:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"d1a3b768-4d98-4c18-88c8-89bdc4543451","html_url":"https://github.com/memphis-tools/dockerize_django_app_on_digitalocean","commit_stats":null,"previous_names":["memphis-tools/dockerize_django_app_on_digitalocean"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memphis-tools%2Fdockerize_django_app_on_digitalocean","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memphis-tools%2Fdockerize_django_app_on_digitalocean/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memphis-tools%2Fdockerize_django_app_on_digitalocean/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/memphis-tools%2Fdockerize_django_app_on_digitalocean/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/memphis-tools","download_url":"https://codeload.github.com/memphis-tools/dockerize_django_app_on_digitalocean/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248800053,"owners_count":21163404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["digitalocean-droplets","django-project","docker","postgresql","python-3-10","terraform"],"created_at":"2024-11-08T00:33:00.637Z","updated_at":"2025-09-23T09:41:10.688Z","avatar_url":"https://github.com/memphis-tools.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Screenshot](https://img.shields.io/badge/python-v3.12.8-blue?logo=python\u0026logoColor=yellow)\n![Screenshot](https://img.shields.io/badge/django-v4.2.18-blue?logo=django\u0026logoColor=yellow)\n![Screenshot](https://img.shields.io/badge/postgresql-v16-blue?logo=postgresql\u0026logoColor=yellow)\n![Screenshot](https://img.shields.io/badge/docker--blue?logo=docker\u0026logoColor=yellow)\n![Screenshot](https://img.shields.io/badge/terraform--blue?logo=hashicorp\u0026logoColor=yellow)\n![Screenshot](https://img.shields.io/badge/digitalocean--black?logo=digitalocean\u0026logoColor=yellow\u0026color=blue)\n![Screenshot](https://img.shields.io/badge/gitlab-ci:cd-blue?logo=gitlab\u0026logoColor=yellow)\n\n\n# Dockerize a dummy Django app\n\n## Description\n\nLast update: **2025/01/18**\n\nDummy Django application which simulates a micro social network where people publish or ask for literary criticisms.\n\n**Learning purposes.**.\n\nThe application can be deployed on local containers through Compose.\n\nIt can be deploy on a cloud provider too, here DigitalOcean for the example, as a 1 node swarm cluster running 3 services (we use docker secrets).\n\nThe project is bind to Gitlab where a CI/CD \"test, build, deploy\" chain run.\n\nDjango particularity set: 'DJANGO_ALLOWED_HOSTS' is a coma separated string to declare host. Only 1 is used, the first one (see settings.py).\n\n**Right now / Still waiting for update (for a secure efficient deployment on cloud)**:\n  - no HTTPS, application is reached through HTTP on 5555 port.\n  - docker secrets are set on the virtual machine manually before run the CI/CD chain. No vault or mount points.\n  - docker secrets access is /are delivered for such path: /run/secrets/*. Problem is than it can be read by any one connected on the container.\n  - initial deployment requires a manual terraform execution in order to publish on the cloud provider.\n  - no dynamic IP /DNS detection or recognition: we create 3 containers /services successively, on the default Swarm network.\n  - no dissociate deployment environments (1 context for development, 1 for production etc).\n  - Gitlab project is create manually, not through API.\n  - Gitlab environment variables are created manually.\n  - Compose could be used, but here we have a Swarm and the DNS hosts are not set automatically. We force '/etc/hosts' update with the 'docker service create' directive '--host'.\n  - no tests's thresholds controlled.\n  - Docker daemon is not rootless.\n\n## Technologies\nPython 3\n\nPostgresql 16 (driver psycopg 3)\n\nGunicorn\n\nNginx (nginxinc/nginx-unprivileged image)\n\nDocker, DockerHub\n\nGitlab (the CI/CD chain is engaged throuh the Gitlab repo)\n\nTerraform\n\n## About it\n\nGoal is to have a 'flexible and functional' Django's skeleton application that follows best practices. Ready to be run through containers.\n\n## Documentation, sources\n\nNotice we use the default 'postgres:16.0-alpine' image. **We do not update the 'pg_hba.conf' file**.\n\nNginx publish port 5555, but the local container use 8080 (default for nginxinc/nginx-unprivileged image).\n\nImplementation of Docker secrets: [DOCKER SECRETS](https://docs.docker.com/engine/swarm/secrets/)\n\nAbout CI/CD, the most reliable and precise i fund: [TECHWORLD WITH NANA](https://www.youtube.com/@TechWorldwithNana)\n\nAbout Postgresql and Docker secrets (look at image official docs): [POSTGRESQL and DOCKER SECRETS](https://hub.docker.com/_/postgres)\n\nAbout Django and Docker secrets. Don\"t be fooled, Django vars expect a value, no usage of the \"_FILE\" suffix. [Django and a Docker secret example](https://en.ovcharov.me/2021/09/30/use-docker-secrets-in-django/)\n      Note: if link breaks, just notice that you must not try to declare a \"SECRET_KEY_FILE\" for example, there is no such mechanism as you could observ for MYSQL or POSTGRESQL.\n\nOther various links:\n\nhttps://docs.docker.com/develop/security-best-practices/\n\nhttps://docs.docker.com/develop/develop-images/dockerfile_best-practices/\n\nhttps://docs.docker.com/engine/reference/commandline/service_create/\n\nhttps://docs.docker.com/engine/security/rootless/\n\nhttps://docs.docker.com/engine/swarm/secrets/\n\nhttps://blog.stephane-robert.info/post/docker-secrets/\n\nhttps://blog.gitguardian.com/hunting-for-secrets-in-docker-hub/\n\nhttps://github.com/GitGuardian/ggshield\n\nhttps://hub.docker.com/r/nginxinc/nginx-unprivileged\n\n## How it works ?\n\nNginx is the front-end, runs as a proxy for Gunicorn. It allows to serve the 'static and media' files.\n\nDjango is served by Gunicorn. They both constitute a service.\n\nPostgresql is the database back-end. It dialogues with Django.\n\n## How use it ?\n\nThe common requirement should to have create a [DockerHub account](https://hub.docker.com/)\n\n---\n\n### Local deployment\n\nYou must have installed docker-compose.\n\nActually see the local deployment capability as a development environment. Here we set .env (not in the cloud deployment).\n\n1. Clone the repository\n\n      `git clone https://github.com/memphis-tools/dockerize_django_app_on_digitalocean.git`\n\n      `cd dockerize_django_app_on_digitalocean`\n\n2. Setup a virtualenv\n\n      `python -m venv env`\n\n      `source env/bin/activate`\n\n      `pip install -U pip`\n\n      `pip install -r dummy_app/requirements.txt`\n\n3. Create a '.env' file in the app folder\n\nFile content:\n\n      DEBUG=0\n      SECRET_KEY='SuperSecretKeyToSetByYourself'\n      DJANGO_ALLOWED_HOSTS='0.0.0.0'\n      SQL_ENGINE=django.db.backends.postgresql\n      SQL_DATABASE=dummy_app_django\n      SQL_USER=postgres\n      SQL_PASSWORD=SuperPasswordToSetByYourself\n      SQL_HOST=db\n      SQL_PORT=5432\n      POSTGRES_USER=postgres\n      POSTGRES_PASSWORD=SuperPasswordToSetByYourself\n      POSTGRES_DB=dummy_app_django\n      SUPERUSER_NAME=admin\n      SUPERUSER_EMAIL=admin@somebluelake.fr\n\nThen execute: \"./docker-compose-local-deployment.sh\"\n\nEither you (re)initialize application:\n\n    ./docker-compose-local-deployment.sh reset\n\nEither you stop and restart application:\n\n    ./docker-compose-local-deployment.sh reload\n\n---\n\n### Cloud deployment\n\nYou need a Gitlab and DigitalOcean account to perform the following.\n\nAs a learning purposes we have a simple iterating execution creation:\n\n- Postgresql host is the first created with a default ip: 172.17.0.2\n- Nginx host is the second created with a default ip: 172.17.0.3\n- Web host (Gunicorn and Django) is the last one create, with a default ip: 172.17.0.4\n\n#### Use terraform to create a 'droplet' on DigitalOcean\n\n[Install Hashicorp's terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli)\n\nCheck the terraform/debian_with_docker.tf, we will create a minimal install from the France region.\n\nYou have a DigitalOcean account, then create a personal access token, and a ssh dedicated key-pair.\n\n  Export your DigitalOcean personal access token:\n\n  `export DO_PAT=\"dop_v1_2WhatADopSecretIsNotItAsLongItIs\"`\n\n  Initialize terraform:\n\n  `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform init`\n\n  Create terraform plan:\n\n  `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform plan -var \"do_token=${DO_PAT}\" -var \"pvt_key=[path to your private key]\"`\n\n  Execute the plan,\n\n  `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform apply -var \"do_token=${DO_PAT}\" -var \"pvt_key=[path to your private key]\" -auto-approve`\n\n  Watch out the end of execution and because it succeeded, consult the following.\n\n  `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform state list`\n\n  `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform state show digitalocean_droplet.dummy-django-with-docker`\n\n  Notice the ipv4_address and ipv4_address_private addresses. Source your ssh private key and login through ssh.\n\n  `ssh root@PublicIpAddress`\n\n  Touch 2 files (on the remote host after ssh connection succeeded):\n\n  `[dockerize_django_app_on_digitalocean]$ touch dummy_app_django_db_secret.txt`: copy /paste in it your POSTGRESQL_PASSWORD\n\n  `[dockerize_django_app_on_digitalocean]$ touch dummy_app_django_web_secret.txt`: copy /paste in it your Django SECRET_KEY\n\nSo you should be able to obtain the below arborescence and informations displayed (we set the 'droplet' name as 'dummy-ops') :\n\n    `dummy-ops:~# pwd\n    /root\n    dummy-ops:~# ls -l\n    total 12\n    -rw-r--r-- 1 root root  11 Sep 14 20:01 dummy_app_django_db_secret.txt\n    -rw-r--r-- 1 root root  65 Sep 14 20:01 dummy_app_django_web_secret.txt\n\n    # notice: eth0's \"159.65.122.181\" is the public ipv4 address (manually declared on Gitlab as ETH0_SWARM_MANAGER_IP).\n    The 5555 port is served through the published port of the Nginx service (set in the 'docker-compose.yml' file). Waiting for TLS the current port translation is *:5555-\u003e*:8080\n    # notice: eth1's \"10.135.0.2\" is a private ipv4 address (manually declared on Gitlab as ETH1_SWARM_MANAGER_IP).\n\n    dummy-ops:~# ip -br -4 a\n    lo               UNKNOWN        127.0.0.1/8\n    eth0             UP             159.65.122.181/20  \u003c\u003c the public ipv4 address. So here we would have a http://159.65.122.181:5555 as index page\n    eth1             UP             10.135.0.2/16      \u003c\u003c The one used while publishing the Swarm (dummy-ops:~# docker swarm init --advertise-addr 10.135.0.2)\n    docker0          DOWN           172.17.0.1/16\n\n    dummy-ops:~# cat dummy_app_django_db_secret.txt\n    @pplepie94`\n\n**Notice the bad pratice here**: i hardcode set 2 secrets plain texts, waiting to be set and used as secrets during the Gitlab CI/CD execution, whereas they could /should be removed after docker secret's creation.\n\nRun a pipeline.\n\n![Screenshot](illustrations/dummy_app_gitlab_pipeline.png)\n\nAfter the deployment, you add some dummy initial datas as users, text and pictures. Use your web.1 id:\n\n    `dummy-ops:~# docker exec web.1.uqj7sxdugvyex93tr4tvkd8mr python ./manage.py init_app_litreview`\n\n![Screenshot](illustrations/dummy_app_init_app.png)\n\nOpen a browser, and log with one of the dummy user created with the init_app_litreview management command.\n\n![Screenshot](illustrations/dummy_app_on_digitalocean_1.png)\n\nBy the way, finally, you can destroy the 'droplet' the way you created it by running:\n\n `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform plan -destroy -out=terraform.tfplan -var \"do_token=${DO_PAT}\" -var \"pvt_key=[path to your private key]\"`\n\n `[dockerize_django_app_on_digitalocean]$ terraform -chdir=terraform apply terraform.tfplan`\n\n ![Screenshot](illustrations/dummy_app_destroy_droplet.png)\n\n#### Setup the Gitlab project\n\nYou have to create the project on Gitlab directly or import it from Github.\n\nThen declare the following **ENV variables in your Gitlab project**. These will be fully used at **test and build stages** (Gitlab CI/CD) only:\n\n    DJANGO_ALLOWED_HOSTS, POSTGRES_DB, POSTGRES_PASSWORD,REGISTRY_USER, REGISTRY_PASSWORD, SSH_KEY, ETH0_SWARM_MANAGER_IP, ETH1_SWARM_MANAGER_IP\n\n![Screenshot](illustrations/gitlab_env_variables.png)\n\n    REGISTRY_USER and REGISTRY_PASSWORD: Your DockerHub credentials\n\n    SSH_KEY: the content of your private SSH key.\n\n    ETH0_SWARM_MANAGER_IP: the public ip address which comes from the 'droplet' (virtual machine) created. The eth0 ipv4 address. Don't hardcode eth0 instead of ip (for Linux). In the example: 159.65.122.181.\n\n    ETH1_SWARM_MANAGER_IP: the private ip address from the droplet. In the example: 10.135.0.2.\n\n![Screenshot](illustrations/dummy_ops_addresses.png)\n\n---\n\n### Github actions: workflow\n\n![Screenshot](illustrations/gitlab_pull_checks.png)\n\nThe ggshield python's package is not in the requirements.txt.\n\n![Screenshot](illustrations/gitguardian_scan.png)\n\nIf you want to scan the 3 images used in this public project:\n\n    memphistools/public_repo:dockerize_django_app_on_digitalocean_db\n\n    memphistools/public_repo:dockerize_django_app_on_digitalocean_nginx\n\n    memphistools/public_repo:dockerize_django_app_on_digitalocean_web\n\n---\n\n### About project usages\n\n3 default accounts with publications will be created: **donald, daisy et loulou**. Password is **applepie94**.\n\nAn admin (\"Django superuser\") account is created too.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmemphis-tools%2Fdockerize_django_app_on_digitalocean","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmemphis-tools%2Fdockerize_django_app_on_digitalocean","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmemphis-tools%2Fdockerize_django_app_on_digitalocean/lists"}