HOW TO Start (Python) Project
(파이썬으로) 프로젝트 시작할 때 필요한 것들
July. 4, 2020
@DoonDoony
Table of contents
🔨 Basic Project Settings
🐳 Docker Settings
🗂 Workflow Settings
🚀 HOW TO START (PYTHON) PROJECT
WHAT TO MAKE
📥 독립된 파이썬 런타임 사용을 위한 파이썬 가상환경 설정
🌸 좀 더 예쁜 커밋 메시지를 만드는 gitmoji
🔨 동일한 코드 컨벤션을 지킬 수 있도록 코드 포매터
📠 올바른 코드 작성을 위한 정적 타입 분석기
📦 최신 패키지 매니저를 통한 프로젝트 관리
🚔 보안/민감 정보를 노출하지 않고 관리
⚙️ 운영 환경 각각의 설정 파일 관리
Basic Course
🚀 HOW TO START (PYTHON) PROJECT
WHAT TO MAKE
🐳 빠른 온보딩과 일관된 개발 환경 구축을 위한 도커 설정
✅ 최소한의 CI 환경 구축
🚕 코드로 CD 환경 관리하기 (w/ Fabric)
🏷 Git tag 간단하게 사용해보기
👩💻 업무를 처리하는 방법에 대한 주관적인 전략 (Work Flow)
advanced Course
🚀 HOW TO START (PYTHON) PROJECT
Oh, Why?
다음 프로젝트를 만들 때 On-Boarding 비용이 절감됩니다
사소한 문제를 미리 발견하고, 최대한 자동으로 해결되도록 할 수 있습니다
오픈소스의 프로젝트 구성에 대해 이해할 수 있습니다 (언어/프레임워크별 상이)
다른 사람과 협업할 때 규칙을 가지고 일할 수 있습니다
개발/이슈에 대한 히스토리 관리가 가능합니다
Q. 이걸 알면 어떤 장점이 있나요?
🚀 HOW TO START (PYTHON) PROJECT
Oh, Why?
라고 프로젝트 세팅만 해놓고 맨날 시작도 안하는 방망이 깎는 노인 2년차가 말합니다
🔨 Basic project settings
Git, Python Virtual Environment, Code Formatter, Linter, ...
🚀 HOW TO START (PYTHON) PROJECT
Create Virtual Env
Pyenv 를 사용하여 가상환경을 설정해요
🚀 HOW TO START (PYTHON) PROJECT
Create Virtual Env (1)
# MacOS에 기본으로 설치된 System Python을 기준으로 설치되어야 합니다
$ python --version
Python 2.7.16
# HomeBrew를 사용한 설치는 권장하지 않습니다 (Dependency로 Python3 런타임이 설치됨)
# pyenv-virtualenv 플러그인 같이 설치되어서 좋습니다
$ curl https://pyenv.run | bash
# 아래 PATH와 Pyenv Hook을 .zshrc에 복붙해주세요
$ cat <<EOF >> .zshrc
$ export PATH="$HOME/.pyenv/bin:$PATH"
$ eval "$(pyenv init -)"
$ eval "$(pyenv virtualenv-init -)"
$ EOF
# Reloading .zshrc
$ source ~/.zshrc
# Check pyenv installation
$ pyenv --version
pyenv 1.2.18
🚀 HOW TO START (PYTHON) PROJECT
Create Virtual Env (2)
# pyenv를 사용해서 Python 3.8.3 을 설치합니다
$ pyenv install 3.8.3
# pyenv-virtualenv 플러그인을 사용해서 프로젝트용 가상환경을 만듭니다
$ pyenv virtualenv 3.8.3 creamheroes
# 설치 가능한 pyenv의 Python 버전은 다음과 같이 조회합니다
$ pyenv install --list
# 만들었던 가상환경은 다음과 같이 제거할 수 있습니다
$ pyenv uninstall creamheroes
# 설치했던 Python 버전은 다음과 같이 제거할 수 있습니다
$ pyenv uninstall 3.8.3
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
프로젝트 디렉토리를 만들고 Git 설정을 해보아요
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
# 디렉토리 생성
~ $ mkdir -p Tutorial/creamheroes
~ $ cd Tutorial/creamheroes
# Git 저장소 만들기
~/creamheroes $ git init
# Git local config로 Author, Email 값 설정하기
~/creamheroes $ git config --local user.name "DoonDoony"
~/creamheroes $ git config --local user.email "cream.doondoon@gmail.com"
# .gitignore 생성 (https://gitignore.io API를 사용)
~/creamheroes $ curl https://www.toptal.com/developers/gitignore/api/macos,python,django,pycharm,git,vim > .gitignore
# pyenv 자동 활성화를 위한 .python-version 파일 생성
~/creamheroes $ echo 'creamheroes' > .python-version
(creamheroes) ~/creamheroes $ python --version
Python 3.8.3
# 또는 다음과 같이 활성화 할 수 있음
~/creamheroes $ pyenv shell creamheroes
(creamheroes) ~/creamheroes $ python --version
Python 3.8.3
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
여기까지 작업했으니까 Commit 하...기 전에
좀 더 Commit Message 를 예쁘게 적기 위해
🌸 gitmoji 를 설치해봐요
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
# NPM Global 설치로 gitmoji-cli 설치
$ npm i -g gitmoji-cli
$ gitmoji --version
3.2.6
# 프로젝트 디렉토리에서
(creamheroes) ~/creamheroes $ gitmoji --init
✔ Gitmoji commit hook created successfully
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
Gitmoji 설치했으니 이제 진짜로 첫 Commit?
Commit 전에 여러가지 문제를 미리 발견해주는
pre-commit 훅도 같이 적용해봐요
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
# 시스템 전역에 파이썬 패키지 매니저인 Poetry를 설치합니다
# NOTE: 반드시 시스템 기본 Python 런타임으로 설치하도록 pyenv 설정을 변경해주세요
$ pyenv shell system
$ python --version
Python 2.7.16
# Poetry는 기본 Python 패키지 매니저인 pip가 아니라, 공식 홈페이지에서 권장하는 방법으로 설치합니다
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
$ poetry --version
Poetry version 1.0.5
# 현재 프로젝트에 Poetry를 활성화 하고 pre-commit을 설치합니다
$ poetry init
This command will guide you through creating your pyproject.toml config.
# ...이하 생략
# DevDependency로 설치합니다
$ poetry add -D pre-commit
# pre-commit 실행에 필요한 훅을 설치합니다
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
# 아래 설정을 추가해서 Commit 되기 전에 아래 항목들을 체크하도록 합니다
# XXX: Python 관련 설정은 추후에 추가합니다
(creamheroes) ~/creamheroes $ cat <<EOF >> .pre-commit-config.yaml
$ default_language_version:
$ python: python3.8
$ repos:
$ - repo: https://github.com/pre-commit/pre-commit-hooks
$ rev: v2.5.0
$ hooks:
$ - id: check-byte-order-marker
$ - id: trailing-whitespace
$ - id: end-of-file-fixer
$ - id: check-yaml
$ - id: check-added-large-files
$ EOF
# 억지로 pre-commit 훅에 걸리게 하려고 파일의 EOF를 지워볼게요
$ truncate -s -1 .gitignore
# 해당 변경 사항을 커밋하면서 정상적으로 동작하는지 확인합니다
(creamheroes) ~/creamheroes $ git commit
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Failed
- hook id: end-of-file-fixer
- exit code: 1
- files were modified by this hook
Fixing .gitignore
Check Yaml...............................................................Passed
Check for added large files..............................................Passed
# 다시 add 할 때 EOF가 자동으로 추가된것이 보입니다
$ git add -p
$ git commit
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
여기까지 작업했으니 진짜 Commit 하고
Github에서 리모트 저장소도 하나 생성해볼게요
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
🚀 HOW TO START (PYTHON) PROJECT
Create Project w/ Git
# 리모트 저장소를 로컬에 ref로 등록하고, 현재까지의 작업내용을 Push 합니다
(creamheroes) ~/creamheroes $ git remote add origin https://github.com/DoonDoony/creamheroes.git
(creamheroes) ~/creamheroes $ git push origin master
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
Code Quality와 Convention을 맞춰주는
Code Formatter, Linter, 정적 분석기를 설치합니다
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
Code Formatter 인 black
자동으로 import 순서를 정렬해주는 isort
사용하지 않는 변수나 import를 자동으로 정리해주는 autoflake
PEP Guide에 맞지 않는 코드를 알려주는 flake8
마지막으로, 파이썬의 정적타입 분석기인 mypy을 설치합니다
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
# 실제 운영에서는 사용하지 않는 라이브러리이므로, DevDependency로써 설치합니다
(creamheroes) ~/creamheroes $ poetry add -D black isort autoflake flake8 flake8-django mypy django-stubs
# 위의 도구들은 setup.cfg파일의 설정을 따릅니다. ini와 비슷한 형식으로 작성합니다
# https://packaging.python.org/guides/distributing-packages-using-setuptools/#setup-cfg
(creamheroes) ~/creamheroes $ cat <<EOF >> setup.cfg
[flake8]
# B = bugbear
# E = pycodestyle errors
# F = flake8 pyflakes
# W = pycodestyle warnings
# B9 = bugbear opinions,
# ISC = implicit str concat
select = B, E, F, W, B9, ISC
ignore = E203, E266, E501, W503, B305, W504
max-line-length = 120
exclude = **/migrations/*
[isort]
combine_as_imports = true
default_section = THIRDPARTY
include_trailing_comma = true
line_length = 79
multi_line_output = 5
use_parentheses = true
[mypy]
python_version = 3.8
warn_return_any = True
disallow_untyped_defs = True
ignore_missing_imports = True
plugins = mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = creamheroes.conf.settings.base
[mypy_django_plugin]
ignore_missing_settings = true
ignore_missing_model_attributes = True
[mypy-*.migrations.*]
# Django migrations should not produce any errors:
ignore_errors = True
[tool:pytest]
addopts = -v -p no:warnings --nomigrations --cov=. --no-cov-on-fail
DJANGO_SETTINGS_MODULE = creamheroes.conf.settings.test
python_paths = creamheroes
console_output_style = progress
cache_dir = .pytest_cache
EOF
# Python은 아직도 발전중이기 때문에... 지들 멋대로 설정파일을 정의하는 부분이 다릅니다
# https://github.com/psf/black/issues/688
# 'black' 코드 포매터는 'pyproject.toml' 파일에 그 설정을 정의할 수 있습니다
(creamheroes) ~/creamheroes $ : << 'END_COMMENT'
[tool.black]
line-length = 119
target-version = ['py38']
include = '\.pyi?$'
exclude = '''
(
/(
\.eggs # exclude a few common directories in the
| \.git # root of the project
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| \.python-version
| _build
| buck-out
| build
| dist
)/
)
'''
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
END_COMMENT
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
설정은 정의 했으니, 아래 세 군데에서 이것을 실행하면 좋을것 같아요
1. PyCharm
2. pre-commit hook
3. CI (Github Actions) 도구 (나중에)
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
# .pre-commit-config.yaml에 아래 설정들을 추가합니다
(creamheroes) ~/creamheroes $ cat <<EOF >> .pre-commit-config.yaml
- repo: https://github.com/python/black
rev: 19.10b0
hooks:
- id: black
- repo: local
hooks:
- id: isort
name: isort
entry: python -m isort.__main__
language: system
types: [python]
- repo: local
hooks:
- id: autoflake
name: autoflake
entry: autoflake
language: system
args: ['--in-place', '--remove-all-unused-imports']
- repo: local
hooks:
- id: flake8
name: flake8
entry: flake8
language: python
types: [python]
args: ['--config', 'setup.cfg']
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
language: python
types: [python]
args: ['--config-file', 'setup.cfg']
EOF
🚀 HOW TO START (PYTHON) PROJECT
Code Format / Lint
작업이 끝났으니 Commit 합시다
.py 파일이 Stage에 없으면, 훅은 동작하지 않아요
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
이제 Django Project 를 생성해봐요
의존성을 설치하고, CLI를 통해 프로젝트를 생성합니다
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
# 최신 버전 django를 설치합니다
(creamheroes) ~/creamheroes $ poetry add django
# django-admin을 통해 프로젝트를 생성합니다
# 반드시 맨 마지막에 현재 경로에 설치를 의미하는 온점 (.) 을 붙여줍니다
(creamheroes) ~/creamheroes $ django-admin startproject creamheroes .
(creamheroes) ~/creamheroes $ lt --level 2
Permissions Size User Date Created Name
drwxr-xr-x - doon 2020-07-04 08:32 .
drwxr-xr-x - doon 2020-07-04 08:33 ├── creamheroes
.rw-r--r-- 0 doon 2020-07-04 08:33 │ ├── __init__.py
.rw-r--r-- 399 doon 2020-07-04 08:33 │ ├── asgi.py
.rw-r--r-- 3,103 doon 2020-07-04 08:33 │ ├── settings.py
.rw-r--r-- 753 doon 2020-07-04 08:33 │ ├── urls.py
.rw-r--r-- 399 doon 2020-07-04 08:33 │ └── wsgi.py
.rwxr-xr-x 631 doon 2020-07-04 08:33 ├── manage.py
.rw-r--r-- 2,137 doon 2020-07-04 08:33 ├── poetry.lock
.rw-r--r-- 269 doon 2020-07-04 08:32 ├── pyproject.toml
.rw-r--r-- 269 doon 2020-07-04 08:32 └── setup.cfg
# settings 파일을 옮겨줍니다. settings 파일은 다른 프레임워크에 비유하면...
# Lavavel의 Config 디렉토리 또는 Spring의 pom.xml? application.properties? 잘 모르겠네요
(creamheroes) ~/creamheroes $ mkdir -p creamheroes/conf/settings
(creamheroes) ~/creamheroes $ cd creamheroes
(creamheroes) ~/creamheroes/creamheroes $ mv __init__.py asgi.py settings.py urls.py wsgi.py conf/
(creamheroes) ~/creamheroes/creamheroes $ touch conf/settings/__init__.py
(creamheroes) ~/creamheroes/creamheroes $ mv conf/settings.py conf/settings/base.py
# Ripgrep 으로 필요한 부분을 찾아바꿔줍니다
(creamheroes) ~/creamheroes/creamheroes $ brew install ripgrep
# GNU sed 에서는 동작이 조금 다릅니다 (sed -i 's/find/replace/g' 로도 가능합니다)
(creamheroes) ~/creamheroes/creamheroes $ rg '\bcreamheroes.settings' -l | xargs sed -i '' 's/creamheroes.settings/creamheroes.conf.settings.base/g'
(creamheroes) ~/creamheroes/creamheroes $ rg '\bcreamheroes.urls' -l | xargs sed -i '' 's/creamheroes.urls/creamheroes.conf.urls/g'
(creamheroes) ~/creamheroes/creamheroes $ rg '\bcreamheroes.wsgi' -l | xargs sed -i '' 's/creamheroes.wsgi/creamheroes.conf.wsgi/g'
# 개발 서버가 정상 동작 하는지 실행해 봅니다
(creamheroes) ~/creamheroes/creamheroes $ cd ..
(creamheroes) ~/creamheroes/ $ ./manage.py runserver
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
그럼 이제 프로젝트 세팅도 했으니
Commit을 할 때가 되긴 뭐가 돼!
중요한 정보를 지우고 공개 저장소에 올려야 합니다
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
Django Settings의 SECRET_KEY는
암호화 서명과 해쉬에 사용되는 salt의 역할도 합니다
따라서 공개 저장소에 노출되면 보안에 위험이 있기에
의미없는 임의값으로 변경합니다
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
이런 중요한 정보는 .env 파일을 생성하여 관리합니다
.env 파일에서 값을 읽어 주입해주는
django-environ 라이브러리를 설치하고 사용합니다
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
# django-environ 을 설치합니다
(creamheroes) ~/creamheroes $ poetry add django-environ
(creamheroes) ~/creamheroes $ touch .env
(creamheroes) ~/creamheroes $ cat <<EOF >> .env
SECRET_KEY=<실제 SECRET_KEY>
EOF
# 공개 저장소에 .env 파일 또한 올릴 수 없으므로 (이미 .gitignore에 등록되어 있을거에요)
# 이런 값들이 필요하다는 힌트가 있어야 협업하는 사람이 값을 물어볼 수 있겠죠?
# NOTE: .env.sample 에서 실제 값을 반드시 제거해야 합니다
(creamheroes) ~/creamheroes $ cp .env .env.sample
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
# creamheroes/conf/settings/base.py
import environ # type: ignore
env = environ.Env()
environ.Env.read_env(os.environ.get("ENV_PATH"))
SECRET_KEY = env.str('SECRET_KEY', '********') # 값이 없어도 Default 값을 따르게 됩니다
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
ENV_PATH 라는 환경변수는 없는데 🤔 어떻게 정의할까요?
direnv 를 사용해서, 프로젝트별 환경변수를 구성할 수 있어요
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
# direnv 를 설치합니다
$ brew install direnv
# direnv 훅을 활성화 해야 합니다
$ cat <<EOF >> ~/.zshrc
eval "$(direnv hook zsh)"
EOF
# direnv 를 활성화 하기 위해서는 .envrc 파일이 필요합니다
# .envrc 파일에 우리가 설정하려는 환경변수를 정의합니다
(creamheroes) ~/creamheroes $ touch .envrc
(creamheroes) ~/creamheroes $ cat <<EOF >> .envrc
export ENV_PATH="$PWD/.env"
EOF
direnv: error /Users/doon/Tutorial/creamheroes/.envrc is blocked. Run `direnv allow` to approve its content
# direnv 를 활성화 합니다
$ direnv allow .
direnv: loading ~/Tutorial/creamheroes/.envrc
direnv: export +ENV_PATH
# 환경변수가 잘 등록되었는지 확인합니다
$ env | grep ENV_PATH
ENV_PATH=/Users/doon/Tutorial/creamheroes/.env
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
환경변수로 잘 적용되었는지를
Django Interactive Shell 을 사용해서 확인해봐요
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
$ ./manage.py shell
Python 3.8.3 (default, May 19 2020, 23:35:46)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.conf import settings
>>> settings.SECRET_KEY
'-2u)(2#bx8+#u6&cv7k$war6#)5sts+&rb6j*#)liiyxl0l#na'
🚀 HOW TO START (PYTHON) PROJECT
Start Django Project
이제 진짜 Commit 하고 다음 Docker 구성으로 넘어가보아요
🔥 Advenced project settings
Docker, CI/CD, Git tag, Workflow, Fabric...
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
Docker 를 사용해서 개발서버를
운영과 비슷한 환경에서 실행되도록 설정해보아요
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
먼저, 실제로 배포될 Docker Image 를 만들기위해
Dockerfile 을 작성해야 합니다
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
# Docker 관련된 파일은 다른 디렉토리에 정리하는걸 선호해요
(creamheroes) ~/creamheroes $ mkdir .docker
# 실제로 서버 구동시에는 ./manage.py runserver 가 아닌 gunicorn을 사용하게 됩니다
# Java Tomcat 같은 녀석입니다
(creamheroes) ~/creamheroes $ poetry add gunicorn
# Dockerfile 은 아래와 같이 작성해봤습니다
(creamheroes) ~/creamheroes $ cat <<EOF >> .docker/Dockerfile
FROM python:3.8.3-slim AS build
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends build-essential gcc
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN mkdir /code
WORKDIR /code
COPY requirements.txt .
RUN pip install -U pip
RUN pip install -r requirements.txt
FROM python:3.8.3-slim AS final
COPY --from=build /opt/venv /opt/venv
WORKDIR /code
COPY . .
ENV PATH="/opt/venv/bin:$PATH"
CMD ["gunicorn", "creamheroes.conf.wsgi", "--bind", "0.0.0.0:8000"]
EOF
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
Multi Stage Build 를 활용해 이미지 사이즈를 줄이고
캐시된 데이터를 최대한 활용히 빌드속도를 올립니다
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
Docker Image 를 빌드하기전에
자주 쓸 것 같은 명령어를 Makefile 에 정의해보면
어떨까요잉 🤔
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
# Makefile 에서 사용할 만한 명령어 세트입니다
(creamheroes) ~/creamheroes $ cat <<EOF >> Makefile
.PHONY: build
build:
@make freeze
@docker image build . -t creamheroes:v1 -f .docker/Dockerfile
.PHONY: clean
clean:
@docker image prune -f
@docker container prune -f
@docker network prune -f
@docker volume prune -f
.PHONY: destroy
destroy:
@docker container stop creamheroes || true
@make clean > /dev/null
.PHONY: freeze
freeze:
@poetry export -f requirements.txt -o requirements.txt --dev -n --without-hashes
.PHONY: log
log:
@docker container logs -f creamheroes
.PHONY: open
open:
@open http://localhost:8000
.PHONY: runserver
runserver:
@docker container run --rm -d -p 8000:8000 --name creamheroes -t creamheroes:v1
EOF
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
이제 실제로 이미지를 빌드하고
도커 이미지로 개발서버가 잘 동작하는지
한 번 확인해봐요 🤗
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
# 이미지 빌드 시작
(creamheroes) ~/creamheroes $ make build
# 서버 시작
(creamheroes) ~/creamheroes $ make runserver
# 브라우저에서 확인
(creamheroes) ~/creamheroes $ make open
# 서버 종료
(creamheroes) ~/creamheroes $ make destroy
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
여기서 한 번 커밋하고
오늘은 여기까지... 🏃🏻♂️
🚀 HOW TO START (PYTHON) PROJECT
Create Docker Image
다음에 땜빵할일 있으면 이어서 준비해 볼게요
HOW TO START (PYTHON) PROJECT
By Doon Doon
HOW TO START (PYTHON) PROJECT
Python + Django = 🔥
- 948