19
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Devcontainerを使ってDjangoの環境構築をしよう!

Posted at

はじめに

AWSのECSについて学習するため、フロントエンドとバックエンドの環境を構築することにしました。これまで実務ではNestJSやRailsを扱ってきましたが、ちょうどPythonにも興味を持っていたため、今回はPythonを用いて開発を進めてみることにします。

また、インフラの理解を深める上で、Dockerの知識が不十分だと感じていたため、この機会にしっかりと学び直そうと考えました。加えて、GitHub ActionsやTerraformを活用しながらCI/CDやIaCについても学び、体系的に整理していく予定です。

この学習の過程や得た知識を記録しながら、ブログとしてまとめていきます。拙い部分もあるかもしれませんが、インフラ学習の備忘録として役立つ内容を目指し、できるだけ分かりやすく整理していきたいと思います。同じように学びたい方の参考になれば嬉しいです。

なお、学習しながらブログを書いているため、説明が雑もしくはあまり良くない部分があるかもしれませんが、その点は多めに見ていただけると幸いです。動くものは作れるようになるというところを目標に頑張ります。

作成したリポジトリ

前提

  1. docker周りのセットアップが済んでいる
  2. vscodeの拡張機能 Dev Containers をインストールしている

1. Dockerまわりのファイル作成

作業したい位置に移動し、以下のコマンドを叩く。

mkdir .devcontainer && touch .devcontainer/devcontainer.json && touch .devcontainer/Dockerfile && touch docker-compose.yml

devcontainer.jsonに以下を追記

{
  "name": "Django Development",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "api",
  "workspaceFolder": "/usr/src/app",
  "shutdownAction": "stopCompose",
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "ms-azuretools.vscode-docker",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "aaron-bond.better-comments",
        "janisdd.vscode-edit-csv",
        "usernamehw.errorlens",
        "vscode.git",
        "mhutchie.git-graph",
        "donjayamanne.githistory",
        "gitHub.vscode-pull-request-github",
        "eamodio.gitlens",
        "oderwat.indent-rainbow",
        "ms-ceintl.vscode-language-pack-ja",
        "christian-kohler.npm-intellisense",
        "faissaloux.package-manager-intellisense",
        "christian-kohler.path-intellisense",
        "mechatroner.rainbow-csv",
        "chrmarti.regex",
        "bradlc.vscode-tailwindcss",
        "gruntfuggly.todo-tree",
        "vscode-icons-team.vscode-icons"
      ],
      "settings": {
        "editor.fontSize": 16,
        "editor.tabSize": 2,
        "editor.wordWrap": "on",
        "notebook.output.wordWrap": true,
        "git.enableSmartCommit": true,
        "editor.renderWhitespace": "all",
        "terminal.integrated.scrollback": 6000,
        "editor.minimap.size": "fit",
        "files.trimTrailingWhitespace": true,
        "githubPullRequests.pullBranch": "never",
        "workbench.editorAssociations": {
          "*.csv": "default"
        },
        "files.autoSave": "afterDelay",
        "editor.formatOnSave": true,
        "workbench.tree.enableStickyScroll": false,
        "workbench.tree.stickyScrollMaxItemCount": 1,
        "editor.stickyScroll.maxLineCount": 1,
        "editor.stickyScroll.enabled": false,
        "hediet.vscode-drawio.resizeImages": null,
        "workbench.iconTheme": "vscode-icons",
        "better-comments.multilineComments": true,
        "better-comments.highlightPlainText": false,
        "better-comments.tags": [
          {
            "tag": "!",
            "color": "#FF2D00",
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": false,
            "italic": false
          },
          {
            "tag": "?",
            "color": "#3498DB",
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": false,
            "italic": false
          },
          {
            "tag": "//",
            "color": "#474747",
            "strikethrough": true,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": false,
            "italic": false
          },
          {
            "tag": "todo",
            "color": "#FF8C00",
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": false,
            "italic": false
          },
          {
            "tag": "*",
            "color": "#98C379",
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": true
          },
          {
            "tag": "NOTE",
            "color": "#FFD700", // ゴールド色
            "strikethrough": false,
            "underline": true,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": false
          },
          {
            "tag": "DEBUG",
            "color": "#FF1493", // ディープピンク色
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "#1E1E1E", // 暗い背景色
            "bold": true,
            "italic": true
          },
          {
            "tag": "SECTION",
            "color": "#1E90FF", // ドジャーブルー
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": false
          },
          {
            "tag": "CHAPTER",
            "color": "#32CD32", // ライムグリーン
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": false
          },
          {
            "tag": "=",
            "color": "#1E90FF", // ドジャーブルー(SECTIONと同じ色)
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": false
          },
          {
            "tag": "-",
            "color": "#32CD32", // ライムグリーン(CHAPTERと同じ色)
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": false
          },
          {
            "tag": "END",
            "color": "#FF4500", // オレンジレッド
            "strikethrough": false,
            "underline": false,
            "backgroundColor": "transparent",
            "bold": true,
            "italic": false
          }
        ],
        "git.ignoreRebaseWarning": true,
        "yaml.customTags": [
          "!And",
          "!And sequence",
          "!If",
          "!If sequence",
          "!Not",
          "!Not sequence",
          "!Equals",
          "!Equals sequence",
          "!Or",
          "!Or sequence",
          "!FindInMap sequence",
          "!Base64",
          "!Base64 mapping",
          "!Cidr",
          "!Cidr sequence",
          "!Ref",
          "!Sub",
          "!GetAtt",
          "!GetAZs",
          "!ImportValue",
          "!Select",
          "!Select sequence",
          "!Split",
          "!Split sequence",
          "!Join sequence"
        ],
        "diffEditor.hideUnchangedRegions.enabled": true,
        "workbench.colorTheme": "Night Owl"
      }
    }
  }
}

Dockerfileに以下を追記

# ベースとなるDockerイメージを指定
# ここではPythonのバージョン3.13.0を使用
FROM python:3.13.0

# コンテナ内で作業するディレクトリを指定
# ここでは `/usr/src/app` を作業ディレクトリとして設定
WORKDIR /usr/src/app

# 必要なシステムパッケージをインストール
# - `apt-get update`: パッケージリストを更新
# - `libpq-dev`: PostgreSQLとの接続に必要なライブラリをインストール
# - `rm -rf /var/lib/apt/lists/*`: 不要なキャッシュを削除してコンテナのサイズを削減
RUN apt-get update \
    && apt-get install -y libpq-dev
# Pythonパッケージの依存関係をインストール
# - `COPY backend/requirements.txt .` : `requirements.txt` をコンテナ内にコピー
# - `pip install --upgrade pip`: `pip` を最新バージョンに更新
# - `pip install -r requirements.txt`: `requirements.txt` に記載されたパッケージをインストール
COPY backend/requirements.txt .
RUN pip install --upgrade pip \
    && pip install -r requirements.txt

# コンテナのポート8000番を開放
# Djangoの開発サーバーはデフォルトで8000番ポートを使用するため
EXPOSE 8000

CMD ["sh", "-c", "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]

docker-compose.ymlに以下を追記

services:
  api:
    build:
      context: . # 基準となるパス。docker-compose.ymlがあるカレントディレクトリにいてDockerfileなどを探しに行く。
      dockerfile: .devcontainer/Dockerfile # 使用するDockerfileのパス
    container_name: django_api # コンテナの名前を指定
    command: python manage.py runserver 0.0.0.0:8000 # Django の開発サーバーを起動するコマンド
    volumes: # バインドマウント (ファイルを同期する) (ホストPCのフォルダとコンテナ内のフォルダをリアルタイムで同期)
      - ./backend:/usr/src/app # ホスト(PC)の ./backend/ フォルダ を コンテナの /usr/src/app に接続(マウント)
    ports:
      - "8000:8000" # ホストのポート8000とコンテナのポート8000を紐付ける。Docker コンテナは仮想環境の中で動いているので、デフォルトでは外部(ホストPC)からアクセスできません。ports: を設定すると、ホストPCのポートとコンテナのポートを紐づけ(ポートフォワード)、ホストからアクセスできるようになります。
    depends_on:
      - db  # このコンテナを起動する前に db(PostgreSQL)コンテナを起動する
    env_file:
      - .env # 環境変数を記載した .env ファイルを読み込む

  db:
    image: postgres:16
    container_name: postgres_db
    restart: always  # コンテナが停止した場合に自動で再起動
    environment:  # 環境変数を設定
      POSTGRES_USER: ${POSTGRES_USER}  # ユーザー名(.env ファイルから読み込む)
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}  # パスワード(.env ファイルから読み込む)
      POSTGRES_DB: ${POSTGRES_DB}  # 作成するデータベース名(.env ファイルから読み込む)
    volumes:
      - postgres_data:/var/lib/postgresql/data  # データを永続化するためのボリュームをマウント
    ports:
      - "5432:5432"  # ホストのポート5432とコンテナのポート5432を紐づける(PostgreSQL のデフォルトポート)

volumes:
  postgres_data:  # PostgreSQL のデータを永続化するためのボリューム

.envを作成して追記

echo 'POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=dev
' > .env

backend/requirements.txt

Django と PostgreSQL 用のパッケージをインストールします。

mkdir backend && touch backend/requirements.txt

requirements.txtに以下を追記

Django>=5.1
psycopg2>=2.9

Django プロジェクトを新規作成

  1. コンテナ内に入る

    docker compose run api bash  
    
    root@702045f8a4bd:/usr/src/app# django-admin startproject myapi .
    
    exit
    

現在のツリーはこちらになります

.
├── .devcontainer
│   ├── Dockerfile
│   └── devcontainer.json
├── .env
├── .gitignore
├── backend
│   ├── .env
│   ├── db.sqlite3
│   ├── manage.py
│   ├── myapi
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-310.pyc
│   │   │   ├── __init__.cpython-313.pyc
│   │   │   ├── settings.cpython-310.pyc
│   │   │   ├── settings.cpython-313.pyc
│   │   │   ├── urls.cpython-310.pyc
│   │   │   ├── urls.cpython-313.pyc
│   │   │   ├── wsgi.cpython-310.pyc
│   │   │   └── wsgi.cpython-313.pyc
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── requirements.txt
└── docker-compose.yml

35 directories, 50 files
backend
│   ├── .env

backendフォルダにも.envファイルを作成する

echo 'POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=dev
' > backend/.env

コンテナを起動し、DB操作まで

コンテナを再度クリック
image.png

http://localhost:8000/ にアクセス
image.png

postgresを繋ぐ

pip install python-dotenv

settings.pyを変更

DATABASES = {
    'default': {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": "dev", #ご自身が作成したデータベース名
        "USER": "postgres", #ご自身が設定したユーザー名
        "PASSWORD": "postgres", #ご自身が設定したパスワード
        "HOST": "postgres_db",
        "PORT": "5432",
    }
}
python manage.py makemigrations
python manage.py migrate

image.png

envから読み取る方法は以下
requirements.tsx

Django>=5.1
psycopg2>=2.9
python-dotenv

settings.py

"""
Django settings for myapi project.

Generated by 'django-admin startproject' using Django 5.1.5.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

import os
from pathlib import Path
from os.path import join, dirname
from dotenv import load_dotenv

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

load_dotenv(verbose=True)

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {
    'default': {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": os.getenv("POSTGRES_DB"), 
        "USER": os.getenv("POSTGRES_USER"), 
        "PASSWORD": os.getenv("POSTGRES_PASSWORD"), 
        "HOST": "postgres_db",
        "PORT": "5432",
    }
}

Postテーブルを作りたい

myapi/models.pyを作成し、
以下を記述

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

コマンドを実行してテーブルを作成する

models.py で定義されたモデルの変更を検出し、それを「マイグレーションファイル」として保存します。

python manage.py makemigrations

作成されたマイグレーションファイルを元に、実際にデータベースを更新します。

python manage.py migrate

Django シェルを使い、テーブルができたか確認。

python manage.py shell

以下を実行

from myapi.models import Post

# 新しい投稿を作成
post = Post.objects.create(
    title="初めての投稿",
    content="これはDjangoのPostモデルを使った最初の投稿です!"
)

# 作成したデータを確認
print(post.id, post.title, post.content)

作成されたデータを確認するには、以下のコマンドを実行してください。

print(Post.objects.all())  # すべてのデータを表示

image.png

うまくDB操作できたと思います。

control + Dでコンソールを抜けることができる。

http://localhost:8000/adminに辿り着くまでに途中に起きたエラー集

サーバーが正しく起動しているのに このサイトにアクセスできません

image.png

myapi/settings.py を開いて、以下のように修正。

ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1', 'localhost']

または

ALLOWED_HOSTS = ['*']

エラー文 ModuleNotFoundError: No module named 'myapi.wsgi'

このエラーは Django が myapi.wsgi を見つけられない ことが原因です。
これは、プロジェクトの構造が正しくない、settings.py の WSGI_APPLICATION の設定が間違っている などが原因で発生します。

myapi/wsgi.py がない場合は作成してください。

myapi/wsgi.py の内容

import os
from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapi.settings")
application = get_wsgi_application()

settings.py の WSGI_APPLICATION を確認

WSGI_APPLICATION = 'myapi.wsgi.application'

最終的には以下になる

"""
Django settings for myapi project.

Generated by 'django-admin startproject' using Django 5.1.5.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

import os
from pathlib import Path
from os.path import join, dirname
from dotenv import load_dotenv

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

load_dotenv(verbose=True)

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-j)ejxqmwc@t647)uc$r^hp2%66so_-pzkgws(^wz4w!85!gcq@'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1', 'localhost']

# Application definition

INSTALLED_APPS = [
    'myapi',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

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',
]

ROOT_URLCONF = 'myapi.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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 = 'myapi.wsgi.application'


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {
    'default': {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": os.getenv("POSTGRES_DB"),
        "USER": os.getenv("POSTGRES_USER"),
        "PASSWORD": os.getenv("POSTGRES_PASSWORD"), #
        "HOST": "postgres_db",
        "PORT": "5432",
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

localohost:8000/admin にアクセスできない

superuser を作成(管理者アカウント)

python manage.py createsuperuser

プロンプトが出るので、管理者情報を入力してください。

Username (leave blank to use 'your-user'): admin
Email address: admin@example.com
Password:
Password (again):

パスワードは 8文字以上で英数字を含む 必要があります。

管理画面で Post モデルを管理できるようにするには、admin.py に登録します。なかったら作成。

myapp/admin.py

from django.contrib import admin
from .models import Post

admin.site.register(Post)

これで、管理画面の「Post」からデータを追加・編集できるようになります。

image.png

まとめ

Django に触れるのは初めてで、最初は 設定やエラー対応に苦戦(4〜5時間かかった) しましたが、
なんとなく動く状態には持っていけました。
次は API の作成やプロジェクトの整理 をしながら、より理解を深めていきたいと思います。

まだまだ学ぶことは多いですが、まずは動かしてみることが大事!
引き続き、Django に挑戦していきます!
(AWSを学ぶために始めてるのにDjangoでかなりハマったので選んだのを後悔しています。。)

最終的なディレクトリ構成はこちら

.
├── .devcontainer
│   ├── Dockerfile
│   └── devcontainer.json
├── .env
├── .gitignore
├── backend
│   ├── .env
│   ├── db.sqlite3
│   ├── manage.py
│   ├── myapi
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-313.pyc
│   │   │   ├── admin.cpython-313.pyc
│   │   │   ├── models.cpython-313.pyc
│   │   │   ├── settings.cpython-313.pyc
│   │   │   ├── urls.cpython-313.pyc
│   │   │   └── wsgi.cpython-313.pyc
│   │   ├── admin.py
│   │   ├── asgi.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       ├── 0001_initial.cpython-313.pyc
│   │   │       └── __init__.cpython-313.pyc
│   │   ├── models.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── requirements.txt
└── docker-compose.yml
19
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?