Skip to main content

Command Palette

Search for a command to run...

Guide to Deploying Machine Learning Models with Django and Docker to AWS

Updated
13 min read
Guide to Deploying Machine Learning Models with Django and Docker to AWS

Deploying machine learning models is a crucial step in transforming your trained models into usable applications. Deployment enables you to serve predictions via an API, integrate them with web applications, and make them accessible to end-users. This guide explores how to deploy machine learning models using Django REST Framework (DRF) and Docker. We will cover setting up a Django project, creating endpoints for model prediction, containerizing the application with Docker, deploying it to AWS, setting up CI/CD, and scaling the deployment.

Importance of Model Deployment

Model deployment is essential because it:

  • Transforms models into usable applications: It enables models to serve predictions to users or other systems.

  • Enables integration: Models can be integrated with web applications, mobile apps, or other services.

  • Facilitates scalability and maintenance: Deployment allows for scaling model usage and maintaining version control.

  • Provides Real-time Access: Deployed models can provide real-time predictions, improving decision-making processes.

  • Operationalizes Machine Learning: Deployment brings machine learning models into production, allowing them to impact business processes and operations.

Overview of Django REST Framework and Docker

  • Django REST Framework (DRF): A powerful and flexible toolkit for building Web APIs in Django. It makes it easy to create RESTful APIs and provides various features for serialization, authentication, and view handling.

  • Docker: A platform for developing, shipping, and running applications inside containers. Docker ensures that applications run consistently across different environments by packaging all dependencies and configurations together.

Setting Up the Development Environment

Prerequisites

Before starting, ensure you have the following installed on your system:

  • Python 3.8+

  • pip (Python package installer)

  • Docker

  • Git

  • Virtual Environment: To keep dependencies isolated.

Setting Up a Django Project

  1. Create a Virtual Environment: Creating a virtual environment helps to isolate your project dependencies. This ensures that the packages you install for one project do not interfere with those of another project.

     python -m venv venv
     source venv/bin/activate  # On Windows, use `venv\Scripts\activate`
    
  2. Install Django and DRF: After activating the virtual environment, install Django and Django REST Framework using pip.

     pip install django djangorestframework
    
  3. Create a Django Project: Use the django-admin command to start a new Django project.

     django-admin startproject ml_deploy
     cd ml_deploy
    
  4. Create a Django App: Within the project directory, create a new app called api.

     python manage.py startapp api
    
  5. Add api to INSTALLED_APPS in settings.py: Open ml_deploy/settings.py and add 'api' and 'rest_framework' to the INSTALLED_APPS list.

     INSTALLED_APPS = [
         ...
         'rest_framework',
         'api',
     ]
    

Creating Endpoints for Model Prediction

  1. Create a Serializer (api/serializers.py): Serializers in DRF are used to convert complex data types, such as querysets and model instances, into native Python data types that can then be easily rendered into JSON, XML, or other content types.

     from rest_framework import serializers
    
     class PredictionSerializer(serializers.Serializer):
         input_data = serializers.ListField(
             child=serializers.FloatField()
         )
    
  2. Create a View (api/views.py): Create a view to handle POST requests, which will accept input data, process it using the machine learning model, and return the prediction.

     from rest_framework.views import APIView
     from rest_framework.response import Response
     from rest_framework import status
     from .serializers import PredictionSerializer
     import pickle
    
     class PredictView(APIView):
         def post(self, request, *args, **kwargs):
             serializer = PredictionSerializer(data=request.data)
             if serializer.is_valid():
                 input_data = serializer.validated_data['input_data']
                 prediction = self.make_prediction(input_data)
                 return Response({'prediction': prediction}, status=status.HTTP_200_OK)
             return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
         def make_prediction(self, input_data):
             # Load your pre-trained model
             model_path = 'path/to/your/model.pkl'
             with open(model_path, 'rb') as file:
                 model = pickle.load(file)
             prediction = model.predict([input_data])
             return prediction[0]
    
  3. Create URLs (api/urls.py): Define the URL pattern for the prediction endpoint.

     from django.urls import path
     from .views import PredictView
    
     urlpatterns = [
         path('predict/', PredictView.as_view(), name='predict'),
     ]
    
  4. Include URLs in Project URLs (ml_deploy/urls.py): Include the api URLs in the main project's URL configuration.

     from django.contrib import admin
     from django.urls import path, include
    
     urlpatterns = [
         path('admin/', admin.site.urls),
         path('api/', include('api.urls')),
     ]
    

Handling Input and Output Data

  • Input Data: The input data is handled by the PredictionSerializer, which validates and processes the incoming request data.

  • Output Data: The output data (model prediction) is returned as a JSON response.

Example Test Case

Create a test case to ensure that the prediction endpoint works as expected.

from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status

class PredictViewTestCase(TestCase):
    def setUp(self):
        self.client = APIClient()

    def test_prediction(self):
        input_data = {"input_data": [5.1, 3.5, 1.4, 0.2]}
        response = self.client.post('/api/predict/', input_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('prediction', response.data)

Containerizing with Docker

Writing a Dockerfile

  1. Create a Dockerfile: A Dockerfile contains instructions on how to build a Docker image.

     # Use an official Python runtime as a parent image
     FROM python:3.8-slim
    
     # Set the working directory in the container
     WORKDIR /app
    
     # Copy the current directory contents into the container at /app
     COPY . /app
    
     # Install any needed packages specified in requirements.txt
     RUN pip install --no-cache-dir -r requirements.txt
    
     # Make port 8000 available to the world outside this container
     EXPOSE 8000
    
     # Define environment variable
     ENV DJANGO_SETTINGS_MODULE=ml_deploy.settings
    
     # Run the Django development server
     CMD ["gunicorn", "--bind", "0.0.0.0:8000", "ml_deploy.wsgi:application"]
    
  2. Create a requirements.txt: List all the dependencies required for your Django project in a requirements.txt file.

     django
     djangorestframework
     gunicorn
    

Building and Running Docker Containers

  1. Build the Docker Image: Use the docker build command to create a Docker image from the Dockerfile.

     docker build -t ml_deploy .
    
  2. Run the Docker Container: Use the docker run command to start a container from the built image.

     docker run -p 8000:8000 ml_deploy
    

Best Practices for Containerization

  • Use a .dockerignore File: Exclude unnecessary files from the Docker image to keep it small and secure.

      venv/
      __pycache__/
      .pytest_cache/
      *.pyc
      *.pyo
      .DS_Store
    
  • Minimize Layers: Combine related commands to minimize layers in the Docker image, which can improve build times and reduce the image size.

  • Security: Avoid running containers as the root user. Use non-root users wherever possible to enhance security.

Advanced Docker Configuration

  • Multi-Stage Builds: Optimize your Dockerfile by using multi-stage builds to separate build and runtime environments, reducing the final image size.

      # Stage 1: Build
      FROM python:3.8-slim AS builder
      WORKDIR /app
      COPY requirements.txt .
      RUN pip install --no-cache-dir -r requirements.txt
    
      # Stage 2: Runtime
      FROM python:3.8-slim
      WORKDIR /app
      COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
      COPY . /app
      EXPOSE 8000
      ENV DJANGO_SETTINGS_MODULE=ml_deploy.settings
      CMD ["gunicorn", "--bind", "0.0.0.0:8000", "ml_deploy.wsgi:application"]
    

Continuous Integration (CI) and Continuous Deployment (CD)

Setting Up CI/CD

Continuous Integration (CI)

  1. Choose a CI Service: Popular choices include GitHub Actions, Travis CI, CircleCI, and Jenkins.

  2. GitHub Actions Example:

    • Create a .github/workflows/ci.yml file:

        name: CI
      
        on:
          push:
            branches:
              - main
          pull_request:
            branches:
              - main
      
        jobs:
          build:
            runs-on: ubuntu-latest
      
            steps:
            - name: Checkout code
              uses: actions/checkout@v2
      
            - name: Set up Python
              uses: actions/setup-python@v2
              with:
                python-version: 3.8
      
            - name: Install dependencies
              run: |
                python -m pip install --upgrade pip
                pip install django djangorestframework
                pip install -r requirements.txt
      
            - name: Run tests
              run: python manage.py test
      

Continuous Deployment (CD)

  1. Set Up DockerHub: Create a DockerHub repository for your project.

  2. GitHub Actions for CD:

    • Create a .github/workflows/cd.yml file:

        name: CD
      
        on:
          push:
            branches:
              - main
      
        jobs:
          deploy:
            runs-on: ubuntu-latest
      
            steps:
            - name: Checkout code
              uses: actions/checkout@v2
      
            - name: Log in to DockerHub
              run: echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
      
            - name: Build and push Docker image
              run: |
                docker build -t your-dockerhub-username/ml_deploy:${{ github.sha }} .
                docker push your-dockerhub-username/ml_deploy:${{ github.sha }}
      
            - name: Deploy to EC2
              env:
                DOCKER_IMAGE: your-dockerhub-username/ml_deploy:${{ github.sha }}
              run: |
                ssh -o StrictHostKeyChecking=no -i your-key-pair.pem ec2-user@your-ec2-public-dns << 'EOF'
                docker pull $DOCKER_IMAGE
                docker stop ml_deploy || true
                docker rm ml_deploy || true
                docker run -d -p 8000:8000 --name ml_deploy $DOCKER_IMAGE
                EOF
      

Streamlining Deployment on Staging and Production

  • Environment Configuration: Use environment variables to configure the application for different environments (development, staging, production). Store these variables securely, for instance, using AWS Secrets Manager or environment variable files.

  • Automated Tests: Ensure all tests pass before deploying to staging or production. Include unit tests, integration tests, and end-to-end tests in your CI pipeline to catch any issues early.

  • Staging Environment: Deploy to a staging environment first to perform final checks before production deployment. This environment should closely mirror the production environment to identify any issues that might occur in production.

Advanced CI/CD Configuration

  • Blue-Green Deployment: Implement a blue-green deployment strategy to minimize downtime and reduce risk during deployment. This involves running two identical production environments (blue and green). During deployment, traffic is routed to the new version (green), and if it performs well, it is promoted to production.

  • Canary Deployment: Gradually roll out new versions to a small subset of users before making it available to all users. This approach helps in identifying issues without affecting all users.

  • Rollback Mechanism: Implement a rollback mechanism to quickly revert to the previous stable version if an issue is detected after deployment.

Monitoring and Maintenance

Setting Up Logging and Monitoring

  • Logging: Use Django’s built-in logging framework to log important events and errors. Example configuration in settings.py:

      LOGGING = {
          'version': 1,
          'disable_existing_loggers': False,
          'handlers': {
              'file': {
                  'level': 'DEBUG',
                  'class': 'logging.FileHandler',
                  'filename': '/var/log/django/debug.log',
              },
          },
          'loggers': {
              'django': {
                  'handlers': ['file'],
                  'level': 'DEBUG',
                  'propagate': True,
              },
          },
      }
    
  • Monitoring: Use monitoring tools like AWS CloudWatch, Prometheus, Grafana, or third-party services like Datadog or New Relic to monitor the application’s performance and resource usage. Set up alerts to notify you of any issues.

Updating and Versioning Models

  • Model Versioning: Use a versioning system to keep track of different model versions. Store models with version numbers and update the make_prediction method to load the correct model version.

Example Model Versioning

class PredictView(APIView):
    def make_prediction(self, input_data):
        model_version = 'v1.0'  # Example version
        model_path = f'path/to/your/model_{model_version}.pkl'
        with open(model_path, 'rb') as file:
            model = pickle.load(file)
        prediction = model.predict([input_data])
        return prediction[0]
  • Automated Deployment: Set up a CI/CD pipeline to automate the deployment of updated models and application code.

Database Migrations

  • Using Django Migrations: Django’s migration framework helps you keep track of changes to your models and propagate them to your database schema.

      python manage.py makemigrations
      python manage.py migrate
    
  • Handling Migrations in Docker: Ensure that migrations are run as part of the container startup process.

      CMD ["sh", "-c", "python manage.py migrate && gunicorn ml_deploy.wsgi:application --bind 0.0.0.0:8000"]
    

Backup and Restore

  • Database Backups: Regularly back up your database to prevent data loss. Use AWS RDS automated backups or custom scripts to back up your PostgreSQL database.

  • Restore Procedures: Have a tested procedure in place to restore your database from backups in case of data loss or corruption.

Deploying to AWS

Setting Up an EC2 Instance

  1. Launch an EC2 Instance:

    • Go to the AWS Management Console.

    • Launch a new EC2 instance with the appropriate specifications.

    • Select a suitable Amazon Machine Image (AMI) (e.g., Ubuntu).

  2. Configure Security Groups:

    • Open port 22 for SSH access.

    • Open port 8000 for accessing the Django application.

  3. SSH into the Instance: Use your SSH key pair to connect to the EC2 instance.

     ssh -i your-key-pair.pem ec2-user@your-ec2-public-dns
    

Deploying Docker Containers on EC2

  1. Install Docker: Update the package index and install Docker.

     sudo apt update
     sudo apt install docker.io
     sudo systemctl start docker
     sudo systemctl enable docker
    
  2. Transfer the Docker Image: Use scp or an alternative method to transfer the Docker image to the EC2 instance, or build the Docker image directly on the EC2 instance.

  3. Run the Docker Container: Start the Docker container on the EC2 instance.

     docker run -d -p 8000:8000 ml_deploy
    

Configuring Nginx as a Reverse Proxy

  1. Install Nginx: Install Nginx to act as a reverse proxy for your Django application.

     sudo apt update
     sudo apt install nginx
    
  2. Configure Nginx: Create a new configuration file in /etc/nginx/sites-available/ml_deploy.

     server {
         listen 80;
    
         server_name your-ec2-public-dns;
    
         location / {
             proxy_pass http://localhost:8000;
             proxy_set_header Host $host;
             proxy_set_header X-Real-IP $remote_addr;
             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
             proxy_set_header X-Forwarded-Proto $scheme;
         }
     }
    
  3. Enable the Configuration: Create a symbolic link to enable the configuration and restart Nginx.

     sudo ln -s /etc/nginx/sites-available/ml_deploy /etc/nginx/sites-enabled
     sudo nginx -t
     sudo systemctl restart nginx
    

Securing the Application with SSL/TLS

  1. Install Certbot: Certbot is a free tool to obtain SSL certificates from Let's Encrypt.

     sudo apt install certbot python3-certbot-nginx
    
  2. Obtain and Configure SSL Certificate: Use Certbot to obtain and automatically configure SSL for Nginx.

     sudo certbot --nginx -d your-ec2-public-dns
    

Automating Docker Deployment with Docker Compose

  1. Install Docker Compose: Docker Compose is a tool for defining and running multi-container Docker applications.

     sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
     sudo chmod +x /usr/local/bin/docker-compose
    
  2. Create a docker-compose.yml File: Define your application services, networks, and volumes in a docker-compose.yml file.

     version: '3'
     services:
       web:
         image: ml_deploy
         build: .
         command: gunicorn ml_deploy.wsgi:application --bind 0.0.0.0:8000
         volumes:
           - .:/app
         ports:
           - "8000:8000"
         depends_on:
           - db
       db:
         image: postgres
         volumes:
           - postgres_data:/var/lib/postgresql/data
         environment:
           POSTGRES_DB: mydatabase
           POSTGRES_USER: myuser
           POSTGRES_PASSWORD: mypassword
     volumes:
       postgres_data:
    
  3. Deploy with Docker Compose: Use Docker Compose to build and start your application.

     docker-compose up -d
    

Scaling the Deployment

Horizontal Scaling

  • Load Balancing: Use a load balancer (e.g., AWS Elastic Load Balancing - ELB) to distribute traffic across multiple instances of your application. This ensures high availability and reliability.

    Example Nginx Configuration for Load Balancing:

      upstream django_app {
          server 127.0.0.1:8001;
          server 127.0.0.1:8002;
          server 127.0.0.1:8003;
      }
    
      server {
          listen 80;
          server_name your-ec2-public-dns;
    
          location / {
              proxy_pass http://django_app;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $scheme;
          }
      }
    
  • Auto Scaling: Configure auto-scaling groups in AWS to automatically scale the number of instances based on demand. Auto-scaling ensures that your application can handle varying loads by dynamically adding or removing instances.

Vertical Scaling

  • Increase Instance Size: Upgrade your EC2 instance type to one with more CPU and memory resources. This can improve performance for resource-intensive tasks.

  • Optimize Resource Usage: Ensure efficient use of CPU and memory by optimizing your application and Docker configuration. For example, use caching to reduce database load and optimize your Django application to handle requests more efficiently.

Optimizing Django for Production

  • Use Gunicorn: Use Gunicorn as the WSGI server instead of the built-in Django development server. Gunicorn is more robust and suitable for production environments.

      gunicorn ml_deploy.wsgi:application --bind 0.0.0.0:8000
    
  • Database Indexing: Optimize database queries and add indexes where necessary to improve query performance.

  • Cache Static Files: Use a Content Delivery Network (CDN) to cache static files and improve loading times for users.

Caching Strategies

  • Django Caching Framework: Utilize Django’s caching framework to cache views, templates, and data. This can significantly improve performance by reducing the load on the database.

  • Redis or Memcached: Use Redis or Memcached for caching. These in-memory data stores can cache query results, session data, and other frequently accessed data.

      CACHES = {
          'default': {
              'BACKEND': 'django_redis.cache.RedisCache',
              'LOCATION': 'redis://127.0.0.1:6379/1',
              'OPTIONS': {
                  'CLIENT_CLASS': 'django_redis.client.DefaultClient',
              }
          }
      }
    

Monitoring and Metrics

  • Application Metrics: Collect and analyze application metrics to monitor performance and detect issues. Tools like Prometheus and Grafana can help you visualize metrics and set up alerts.

  • Logging: Use a centralized logging solution like ELK Stack (Elasticsearch, Logstash, Kibana) or Graylog to collect and analyze logs from your application.

Security Best Practices

  • Use HTTPS: Ensure that all communication between clients and your server is encrypted using HTTPS. This protects data in transit from interception and tampering.

  • Environment Variables: Store sensitive information like API keys and database credentials in environment variables or secure secrets management services.

  • Regular Updates: Keep your Django, Docker, and other dependencies up to date with security patches to protect against vulnerabilities.

Disaster Recovery

  • Automated Backups: Implement automated backups for your databases and critical data. Use AWS Backup or similar services to schedule regular backups and ensure data is recoverable.

  • Disaster Recovery Plan: Develop a disaster recovery plan that outlines steps to restore services in the event of a failure. Test the plan regularly to ensure it works as expected.

Advanced Scaling Techniques

  • Microservices Architecture: Consider breaking your application into smaller microservices that can be developed, deployed, and scaled independently.

  • Serverless Computing: Use serverless computing services like AWS Lambda for specific functions or tasks within your application. This can help reduce operational overhead and scale automatically.

Conclusion

In this post, we explored how to deploy machine learning models using Django REST Framework and Docker on AWS. We covered setting up a Django project, creating endpoints for model prediction, containerizing the application with Docker, deploying it to an AWS EC2 instance, setting up CI/CD pipelines, streamlining deployment on staging and production environments, and scaling the deployment. By following these steps, you can efficiently deploy your machine learning models and make them accessible as APIs.

If you want to discuss your MLOps or DevOps process, then feel free to visit me at AhmadWKhan.com.

Resources for Further Learning

More from this blog

A

Ahmad W Khan

117 posts

Changing the world, one line of code at a time.

Deploying Machine Learning Models with Django and Docker on AWS