[Django]-How to update code in a docker container?

11👍

In my experience Docker serves two purposes:

  1. To be able to develop code in a containerized environment. This is very useful as I am now able to get new developers on my team ready to work in about 5 mins Previously, this could have taken anywhere from an hour to several hours for misc issues, especially on older projects.
  2. To be able to package an application in a containerized environment. This is also a great time saver as the only requirement for the environment is to have Docker installed.

When you are developing your code you should mount the source/volume so that your changes are always reflected inside the container. When you want to package an app for deployment you should COPY the source into the container and package it appropriately.

Here is a docker-compose file I use to (1) build an image to develop against, (2) develop my code, and (3) ship it (I’m using spring boot):

version: '3.7'
services:
  dev:
    image: '${MVN_BUILDER}'
    container_name: '${CONTAINER_NAME}'
    ports:
      - '8080:8080'
    volumes:
      - './src:/build/src'
      - './db:/build/db'
      - './target:/build/target'
      - './logs:/build/logs'
    command: 'mvn spring-boot:run -Drun.jvmArguments="-Xmx512m" -Dmaven.test.skip=true'
  deploy:
    build:
      context: .
      dockerfile: Dockerfile-Deploy
      args:
        MVN_BUILDER: '${MVN_BUILDER}'
    image: '${DEPLOYMENT_IMAGE}'
    container_name: '${CONTAINER_NAME}'
    ports:
      - '8080:8080'
  maven:
    build:
      context: .
      dockerfile: Dockerfile
    image: '${MVN_BUILDER}'
    container_name: '${CONTAINER_NAME}'
  1. I would run docker-compose build maven to build my base image. This is needed so that when I run my code in a container all the dependencies are installed in the image. The Dockerfile for this essentially copies to pom.xml into the image and downloads the dependencies needed for the app. Note this would need to be performed anytime dependencies change. Here is the Dockerfile to build that image that is referenced in the maven service:
### BUILD a maven builder. This will contain all mvn dependencies and act as an abstraction for all mvn goals
FROM maven:3.5.4-jdk-8-alpine as builder

#Copy Custom Maven settings
#COPY settings.xml /root/.m2/

# create app folder for sources
RUN mkdir -p /build
RUN mkdir -p /build/logs

# The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile.
WORKDIR /build
COPY pom.xml /build
#Download all required dependencies into one layer
RUN mvn -B dependency:go-offline dependency:resolve-plugins
RUN mvn clean install
  1. Next, I would run docker-compose up dev to start my dev service and begin to develop my application. This service mounts my code into the container and uses Maven to start a spring boot application. Anytime I change the code spring boot restarts the server and my changes are reflected.

  2. Finally, once I am happy with my application I build an image that has my application packaged for deployment using docker-compose build deploy. I use a two-stage build process to first copy the source into a container and package it for deployment as a Jar then that Jar is put into the 2nd stage where I can simply run java -jar build/app.jar (in the container) to start my application and the first stage is removed. That’s It! Now you can deploy this latest image anywhere Docker is installed.

Here is what that last Dockerfile (Dockerfile-Deploy) looks like:

ARG MVN_BUILDER
### Stage 1 - BUILD image
FROM $MVN_BUILDER as builder
COPY src /build/src
RUN mvn clean package -PLOCAL

### Stage 2 - Deploy Jar
FROM openjdk:8
RUN mkdir -p /build
COPY --from=builder /build/target/*.jar /build/app.jar
EXPOSE 8080  
ENTRYPOINT ["java","-jar","build/app.jar"] 

Here the .env file in the same directory as the docker-compose file. I use it to abstract image/container names and simply bump up the version number in one place when a new image is needed.

MVN_BUILDER=some/maven/builder:0.1
DEPLOYMENT_IMAGE=some/deployment/spring:0.1
CONTAINER_NAME=spring-container
CONTAINER_NAME_DEBUG=spring-container-debug

4👍

I think it’s too late to answer your question, however, it might be beneficial for others who reach out.

The tutorial you mentioned is a bit tricky to use for the first-timers, so, I change the structure a little bit. I assume that you have a docker registry account (like Dockerhub) for the purpose of publishing the images to. This is required if you want to access the image on a remote host (you can copy the actual image file but is not recommended).

creating a project

Assume that you are going to create a website with Django and dockerize it, first, you do:

django-admin startproject samplesite

It creates a directory samplesite that includes the following (I added requirements.txt):

db.sqlite3  manage.py  requirements.txt  samplesite

adding Dockerfile and docker-compose.yml

For the Dockerfile, as you can see, nothing is changed compared to the Dockerfile.

FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/

However for the docker-compose.yml:

version: '3'

services:
  db:
    image: postgres
  web:
    build: .
    image: yourUserNameOnDockerHub/mywebsite:0.1  # this line is added
    command: python manage.py runserver 0.0.0.0:8000
    #volumes:
    #  - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

docker-compose.yml is also almost identical to the one mentioned in the tutorial, with volume commented and one line added image: mywebsite:0.1. This line allows us to track the built image and deploy it whenever we want. The volume mounting is not related to the code you write and was put there to take out the dynamic content that is changed by Django (sqlite, uploaded files, etc.).

build and run

If you run docker-compose up the first time everything works fine, however, because of the new line added, when you change the code after the first time, the changes will not reflect in the container that runs. This is because upon each docker-compose up, compose will look for mywebsite:0.1 (that already exists) and does not build a new image and creates a container based on the old one. As we need that image name and tag to publish/deploy our image, we need to instead use:

docker-compose up --build

It will re-build an image with the changes reflected. Every time you make some changes, run it and a new fresh image is created that can be seen with (note that although the name and tag remain unchanged, change in image id shows that this is a new image):

$ docker images
REPOSITORY                                   TAG                 IMAGE ID            CREATED             SIZE
yourUserNameOnDockerHub/mywebsite            0.1                 033c9d2bfac0        7 seconds ago       974MB

publishing and deployment

If you have set up an account on Dockerhub (or any other registry) you can publish the image for later use or deployment on a remote server:

docker push yourUserNameOnDockerHub/mywebsite:01 

If you want to deploy it on a remote host and want to use docker-compose again, Just change the docker-compose.yml to:

version: '3'

services:
  db:
    image: postgres
  web:
    image: yourUserNameOnDockerHub/mywebsite:0.1 
    command: python manage.py runserver 0.0.0.0:8000
    #volumes:
    #  - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

Note that the build: . line is removed (as we are going to run it only). When developing locally, whenever you run docker-compose up --build a new image will be created and tagged and a container based on that will run in the compose stack. If you thought that you are happy with the changes, you follow the publishing step to make it live on the server.

0👍

When you want to update an image, lets say due to your application code changes, you use COPY during your image-build, so in the Dockerfile you do something like

COPY /you/code/on/the/host /var/www

Also see my answer about “volumes” and image-building https://stackoverflow.com/a/39314602/3625317 to clarify why your code is missing in the build

-1👍

In step 9 of the tutorial you set a volume. This volume will link your current directory and your container /code directory. In other words, they will be the same.

So any updates to your local files will change the files in your container as well. Remember that you will need to restart your app so the changes can take place.

Before you deploy your image, you will need to create a second docker compose file. This file will remove the volume so the code will stay inside the container and won’t change from outside. You can follow the steps provided in docker compose documentation.

👤alejdg

Leave a comment