I wrote this article because I wanted to be able to launch Django, React, MySQL, etc. in one shot and develop it just by typing a command in the Docker environment.
This article aims to build an environment from scratch, but if you just want to see the finished version, go to GitHub here: django-react-nginx-mysql-docker (If you do what the README says, it should work)
--Create a project with docker from beginning to end without using any virtual environment
--docker-compose up
will bring up all the containers needed for web development
--Finally deploy to K8s (I think it will be the next article).
There are many articles that start up in a virtual environment at first and then create a docker file so that you can build the environment. However, I find it troublesome to put virtualenv
or yarn
locally, so I would like to manage all the projects with Docker from beginning to end.
--Docker installed --docker-compose installed --Linux or Mac (preferably with a strong CPU and memory)
We will proceed as follows.
Let's start by creating a folder. Create a project folder and type the following command directly under it.
$ mkdir backend
$ mkdir frontend
$ mkdir mysql
$ mkdir mysql_volume
$ mkdir sql
$ touch docker-compose.yml
It should look like this:
$ tree
.
├── backend
├── docker-compose.yml
├── frontend
├── mysql
├── mysql_volume
└── sql
5 directories, 1 file
Create folders for web-back
and nginx
. I'm planning to have nginx
and web-back
in the same pod when deploying to K8s in the future, so this is the configuration. The same is true for web-front
and nginx
at the front.
$ cd backend
$ mkdir web-back
$ mkdir nginx
$ cd web-back
$ touch .env Dockerfile requirements.txt
.env
is a file that contains sensitive information such as API KEY. I don't have a secret key right now, so I'll bury it in the textbook for the time being.
backend/web-back/.env
SECRET_KEY='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
DEBUG=False
Dockerfile for Python environment.
# backend/web-back/Dockerfile
# set base image
FROM python:3.7
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# set work directory
WORKDIR /code
# install dependencies
COPY requirements.txt ./
RUN python3 -m pip install --upgrade pip setuptools
RUN pip install -r requirements.txt
# Copy project
COPY . ./
# Expose application port
EXPOSE 8000
This module is installed with pip.
backend/web-back/requirements.txt
asgiref==3.2.7
Django==3.0.5
django-cors-headers==3.2.1
djangorestframework==3.11.0
gunicorn==20.0.4
psycopg2-binary==2.8.5
python-dotenv==0.13.0
pytz==2019.3
sqlparse==0.3.1
mysqlclient==2.0.2
Go into the nginx folder and create a Dockerfile and a conf file.
I want to separate the files for future deployments, so I'll put a dev
to make them distinguishable.
$ cd ../nginx
$ touch Dockerfile.dev default.dev.conf
This is a Dockerfile for nginx.
backend/nginx/Dockerfile.dev
FROM nginx:1.17.4-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY default.dev.conf /etc/nginx/conf.d
Make sure that all communication that comes to the nginx container is sent to the django container.
backend/nginx/default.dev.conf
upstream django {
server web-back:8000;
}
server {
listen 80;
location = /healthz {
return 200;
}
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
location /static/ {
alias /code/staticfiles/;
}
}
Create Dockerfile and my.cnf directly under the mysql
folder.
$ cd ../../mysql
$ touch Dockerfile my.cnf
I will use 8.0.0
as the version of mysql.
FROM mysql:8.0.0
RUN echo "USE mysql;" > /docker-entrypoint-initdb.d/timezones.sql && mysql_tzinfo_to_sql /usr/share/zoneinfo >> /docker-entrypoint-initdb.d/timezones.sql
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
Write settings such as character code to my.cnf
.
mysql/my.cnf
#Settings for MySQL server
[mysqld]
#Character code/Collation settings
character_set_server=utf8mb4
collation_server=utf8mb4_bin
#Time zone setting
default_time_zone=SYSTEM
log_timestamps=SYSTEM
#Setting the default authentication plugin
default_authentication_plugin=mysql_native_password
#Setting mysql options
[mysql]
#Character code setting
default_character_set=utf8mb4
#mysql client tool settings
[client]
#Character code setting
default_character_set=utf8mb4
Go to the SQL folder and create init.sql
.
$ cd ../sql
$ touch init.sql
sql/init.sql
GRANT ALL PRIVILEGES ON test_todoList.* TO 'user'@'%';
FLUSH PRIVILEGES;
At this point, the file should look like this:
$ tree -a
.
├── backend
│ ├── nginx
│ │ ├── default.dev.conf
│ │ └── Dockerfile.dev
│ └── web-back
│ ├── Dockerfile
│ ├── .env
│ └── requirements.txt
├── docker-compose.yml
├── frontend
├── mysql
│ ├── Dockerfile
│ └── my.cnf
├── mysql_volume
└── sql
└── init.sql
7 directories, 9 files
I will do the front end again later, so I will start up the back end for the time being. Prepare the docker-compose.yml
file as shown below.
docker-compose.yml
version: "3.7"
services:
web-back:
container_name: python-backend
env_file: ./backend/web-back/.env
build: ./backend/web-back/.
volumes:
- ./backend/web-back:/code/
- static_volume:/code/staticfiles # <-- bind the static volume
stdin_open: true
tty: true
command: gunicorn --bind :8000 config.wsgi:application
networks:
- backend_network
environment:
- CHOKIDAR_USEPOLLING=true
- DJANGO_SETTINGS_MODULE=config.local_settings
depends_on:
- db
backend-server:
container_name: nginx_back
build:
context: ./backend/nginx/.
dockerfile: Dockerfile.dev
volumes:
- static_volume:/code/staticfiles # <-- bind the static volume
ports:
- "8080:80"
depends_on:
- web-back
networks:
- backend_network
db:
build: ./mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: todoList
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: 'Asia/Tokyo'
volumes:
- ./mysql_volume:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
networks:
- backend_network
networks:
backend_network:
driver: bridge
volumes:
static_volume:
It's a little difficult because there is a lot of content.
This time I want to make something that works, so I will omit the meaning.
Now let's create a Django project! First, create a project called config
.
$ docker-compose run --rm web-back sh -c "django-admin startproject config ."
Creating backend_web-back_run ... done
etc.....
$ docker-compose run --rm web-back sh -c "python manage.py startapp todo"
Creating backend_web-back_run ... done
If all goes well, you should have created config
and todo
as shown below.
$ tree
.
├── backend
│ ├── nginx
│ │ ├── default.dev.conf
│ │ └── Dockerfile.dev
│ └── web-back
│ ├── config
│ │ ├── asgi.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── Dockerfile
│ ├── manage.py
│ ├── requirements.txt
│ ├── staticfiles
│ └── todo
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
............................
I want to separate the configuration file for the development environment and the production environment, so create a local_setting.py
file in the config
folder. The information in settings.py
will be inherited, and only the database information will be repainted here.
config/local_settings.py
from .settings import *
DEBUG = True
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'todoList',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'db',
'PORT': '3306',
}
}
Let's build with this.
$ docker-compose up --build
Starting python-backend ... done
Starting nginx ... done
Attaching to python-backend, nginx
python-backend | [2020-12-28 14:59:49 +0000] [1] [INFO] Starting gunicorn 20.0.4
python-backend | [2020-12-28 14:59:49 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
python-backend | [2020-12-28 14:59:49 +0000] [1] [INFO] Using worker: sync
python-backend | [2020-12-28 14:59:49 +0000] [10] [INFO] Booting worker with pid: 10
Now let's access localhost: 8080
. You should see the following screen.
When you access the 8080
port, nginx will send communication to the 8000
port. This will give you access to the pages provided by django.
--I want to use rest framework --I want to use the management screen page for operating the API
Since the above has not been done yet, we will prepare for database migration for that purpose. Edit the following three files.
settings.py
todo/models.py
todo/admin.py
Edit settings.py
as follows. At this time, add the cors
part so that the front end can call the back end API later.
config/settings.py
"""
Django settings for config project.
Generated by 'django-admin startproject' using Django 3.0.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
from dotenv import load_dotenv #add to
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.basename(BASE_DIR) #add to
# .Load env
load_dotenv(os.path.join(BASE_DIR, '.env')) #add to
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '1_vj5u9p3nm4fwufe_96e9^6li1htp9avbg8+7*i#h%klp#&0='
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd party
'rest_framework',
'corsheaders',
# Local
'todo.apps.TodoConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
ROOT_URLCONF = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
#Where to refer to static files in the development environment
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] #add to
#Where to reference static files in production
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') #add to
#Media file path
MEDIA_URL = '/media/' #add to
#add to
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
CORS_ORIGIN_WHITELIST = (
'http://localhost',
)
Edit models.py
in todo
.
todo/models.py
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
def __str__(self):
return self.title
Edit admin.py
in todo
.
todo/admin.py
from django.contrib import admin
from .models import Todo
admin.site.register(Todo)
Now run the migration as follows: Also create a superuser. Please set the password etc. as you like.
$ docker-compose run --rm web-back sh -c "python manage.py makemigrations"
Creating backend_web-back_run ... done
Migrations for 'todo':
todo/migrations/0001_initial.py
- Create model Todo
$ docker-compose run --rm web-back sh -c "python manage.py migrate"
Creating backend_web-back_run ... done
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
Applying contenttypes.0001_initial... OK
........
$ docker-compose run --rm web-back sh -c "python manage.py createsuperuser"
Creating backend_web-back_run ... done
username(leave blank to use 'root'):
mail address: [email protected]
Password:root
.........
Set to jump to the admin
and api
pages.
backend/web-back/config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('todo.urls')) #add to
]
You also have to set the URL for todo
. It also creates a serializer file to convert to JSON.
backend/web-back/todo $ touch urls.py serializers.py
urls.py
from django.urls import path, include
from .views import ListTodo, DetailTodo
urlpatterns = [
path('<int:pk>/', DetailTodo.as_view()),
path('', ListTodo.as_view())
]
serializers.py
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('id', 'title', 'body')
Edit the view as well.
views.py
from django.shortcuts import render
# Create your views here.
from django.shortcuts import render
from rest_framework import generics
from .models import Todo
from .serializers import TodoSerializer
class ListTodo(generics.ListAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
class DetailTodo(generics.RetrieveAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
admin
and api
Since css files etc. are not reflected as it is, first organize static files and then start up.
$ cd backend/web-back
$ mkdir static
$ docker-compose run --rm web-back sh -c "python manage.py collectstatic"
Starting ... done
163 static files copied to '/code/staticfiles'.
$ docker-compose up
localhost: 8080/admin
looks like this: Let's log in with the superuser created earlier.
After logging in, you will be taken to a screen where you can manage todos.
I will add it like this.
Now go to localhost: 8080/api/1
and you will find it.
You can now do the following:
--Login to the management screen --Getting data with API
Now you can start building the front end.
If you enter the container and check it as shown below, the data is certainly stored.
$ docker exec -it container_db bash
root@e34e5d2a20e1:/# mysql -u root -p
mysql> use todoList;
mysql> select * from todo_todo;
+----+-------------+--------------+
| id | title | body |
+----+-------------+--------------+
| 1 | do homework | finish maths |
+----+-------------+--------------+
1 row in set (0.00 sec)
It's not necessary right now, but it's all about creating and running test files here. Run the following test file.
backend/web-back/todo/tests.py
from django.test import TestCase
# Create your tests here.
from django.test import TestCase
from .models import Todo
class TodoModelTest(TestCase):
@classmethod
def setUpTestData(cls):
Todo.objects.create(title="first todo", body="a body here")
def test_title_content(self):
todo = Todo.objects.get(id=1)
excepted_object_name = f'{todo.title}'
self.assertEqual(excepted_object_name, 'first todo')
def test_body_content(self):
todo = Todo.objects.get(id=1)
excepted_object_name = f'{todo.body}'
self.assertEqual(excepted_object_name, 'a body here')
Run the test inside the container. It should pass well.
$ docker-compose run --rm web-back sh -c "python manage.py test"
Creating backend_web-back_run ... done
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.005s
OK
Destroying test database for alias 'default'...
Now, let's build the environment of nginx and react + next.js on the front end.
$ cd frontend/
$ mkdir nginx web-front
$ cd nginx
$ touch Dockerfile.dev default.dev.conf wait.sh
The file structure is as follows.
$ cd ../
$ tree
.
├── nginx
│ ├── default.dev.conf
│ ├── Dockerfile.dev
│ └── wait.sh
└── web-front
2 directories, 3 files
The following two files are almost the same as in the backend.
frontend/nginx/default.dev.conf
upstream react {
server web-front:3000;
}
server {
listen 80;
location = /healthz {
return 200;
}
location / {
proxy_pass http://react;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
location /sockjs-node {
proxy_pass http://react;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
frontend/nginx/Dockerfile.dev
FROM nginx:1.17.4-alpine
RUN apk add --no-cache bash
COPY wait.sh /wait.sh
RUN chmod +x /wait.sh
CMD ["/wait.sh", "web-front:3000", "--", "nginx", "-g", "daemon off;"]
RUN rm /etc/nginx/conf.d/default.conf
COPY default.dev.conf /etc/nginx/conf.d
If you proceed as it is, the react container will start up slower than nginx every time, and nginx will mistakenly think that it is a connection error and exit. To prevent it, prepare the following shell file and delay the startup of the nginx container. This file is a copy from the vishnubob/wait-for-it repository.
wait.sh
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# Check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
WAITFORIT_BUSYTIMEFLAG=""
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
# Check if busybox timeout uses -t flag
# (recent Alpine versions don't support -t anymore)
if timeout &>/dev/stdout | grep -q -e '-t '; then
WAITFORIT_BUSYTIMEFLAG="-t"
fi
else
WAITFORIT_ISBUSY=0
fi
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi
docker-compose.yml
version: "3.7"
services:
web-back:
container_name: python-backend
env_file: ./backend/web-back/.env
build: ./backend/web-back/.
volumes:
- ./backend/web-back:/code/
- static_volume:/code/staticfiles # <-- bind the static volume
stdin_open: true
tty: true
command: gunicorn --bind :8000 config.wsgi:application
networks:
- backend_network
environment:
- CHOKIDAR_USEPOLLING=true
- DJANGO_SETTINGS_MODULE=config.local_settings
depends_on:
- db
backend-server:
container_name: nginx_back
build:
context: ./backend/nginx/.
dockerfile: Dockerfile.dev
volumes:
- static_volume:/code/staticfiles # <-- bind the static volume
ports:
- "8080:80"
depends_on:
- web-back
networks:
- backend_network
db:
build: ./mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: todoList
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: 'Asia/Tokyo'
volumes:
- ./mysql_volume:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
networks:
- backend_network
web-front:
image: node:14.13.1
volumes:
- ./frontend/web-front:/home/app/frontend
ports:
- 3000:3000
working_dir: /home/app/frontend
command: [bash, -c, yarn upgrade --no-progress --network-timeout 1000000 && yarn run dev]
networks:
- frontend_network
frontend-server:
container_name: nginx_frontend
build:
context: ./frontend/nginx/.
dockerfile: Dockerfile.dev
ports:
- "80:80"
depends_on:
- web-front
networks:
- frontend_network
networks:
backend_network:
driver: bridge
frontend_network:
driver: bridge
volumes:
static_volume:
The file is now ready.
docker-compose run --rm web-front sh -c "npx create-react-app ."
The web-front should have the following project except node_modules.
$ tree web-front -I node_modules
web-front
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ └── setupTests.js
└── yarn.lock
2 directories, 17 files
Get the modules you need now.
docker-compose run --rm web-front sh -c "yarn add next axios"
docker-compose run --rm web-front sh -c "yarn add --dev typescript @types/react"
Add the dev
item in package.json
. Without this, you will get an error saying that dev
cannot be found.
package.json
"scripts": {
"dev": "next dev", //add to
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Create a pages
folder under src
and put a nice typescript file in it. In next.js, it is a rule to put pages under pages.
pages/index.tsx
import { FC } from 'react'
export default (() => {
return (
<div>
hello world
</div>
)
}) as FC
Now let's do docker-compose up
.
When accessing the front end, just localhost
is fine, no port number is needed. You should see it return hello world
.
--Edit index.tsx
pages/index.tsx
import React, { FC, useEffect, useState } from 'react'
import axios, { AxiosInstance } from 'axios'
type Todo = {
id: string
title: String
body: String
}
export default (() => {
const [todos, setTodo] = useState<Todo[]>([])
const getAPIData = async () => {
let instance: AxiosInstance
instance = axios.create({
baseURL: 'http://localhost:8080',
})
try {
const response = await instance.get('/api/')
console.log(response?.data)
const tododata = response?.data as Todo[]
setTodo(tododata)
} catch (error) {
console.log(error)
}
}
return (
<div>
hello world
<button onClick={getAPIData}>click</button>
{todos.map((item) => (
<div key={item.id}>
<h1>{item.title}</h1>
<p>{item.body}</p>
</div>
))}
</div>
)
}) as FC
When you access localhost
, you will see a button like the one below.
When you press the button, the data will be retrieved through the API.
I don't think I'm using CSS or Bootstrap, but I was able to confirm that the front end and back end can communicate with each other!
For the time being, I may write an article about deploying to K8s in the future.
Deploy Django + React from scratch to GKE (1) Backend development --Nginx + Django wait-for-it.sh
Recommended Posts