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 = 🔥

  • 920