I set up Docker containers for the frontend and backend. I used Docker Compose to manage both containers.
Reasons for Using Docker
I mainly chose to use Docker since it’s a common tool in industry. I also wanted to improve my development process.
- Efficiency: Instead of using multiple commands to set up dependencies and build/run the app, I could use one Docker Compose command.
- Portability: The app will run the same across different machines or environments.
- Clean development: When you start containers fresh, it will avoid issues from previous files or configurations.
- Scalability: I haven’t done this yet, but using Docker containers can make it easier to run multiple instances of the backend and/or frontend to handle more requests.
Backend Container
Copy project files, install dependencies, and start the frontend
# python:3.9-slim includes the Python interpreter but doesn't have unecessary packages
FROM python:3.9-slim
# Install git and other system dependencies
# apt-get update: refresh local package index, which is a
# list of available software. ensures that you are using
# the latest version.
# && run the next command if the previous one worked
# apt-get install -y git: install git package and
# automatically answer yes to any prompts
# rm -rf /var/lib/apt/lists/*: delete temp cache files from
# apt-get update. remove unecessary files to save space
# for Docker image.
RUN apt-get update && apt-get install -y \
git \
&& rm -rf /var/lib/apt/lists/*
# make a directory called "app" in the container. the next
# commands will happen inside this folder.
WORKDIR /app
# copy the requirements.txt file into the container. "." means
# that it will go into the current folder, which in this case
# is app.
COPY requirements.txt .
# install the python libraries in the requirements.txt
# --no-cache-dir: stops pip from saving the cache (installer
# files) inside the image
RUN pip install --no-cache-dir -r requirements.txt
# copy
# first ".": copy files from the current local directory
# second ".": the copied files go into the current container
#directory
COPY . .
# create an environment variable inside the container
# don't output warning messagse from git python
# makes the logs easier to read
# these warnings are usually harmless, so they don't
# affect important functionality features
# GitPython is a library that lets the program do Git
# actions such as cloning and committing
# the Git index (staging area) is a data structure that
# tracks changes
# refreshing the Git index might fail or not be needed inside
# a Docker container. since the app is only reading a git
# repository, you don't need to update the staging area
# anyways.
ENV GIT_PYTHON_REFRESH=quiet
# automatically run this command when the container starts
# uvicorn: start uvicorn web server
# main:app: look for the object called app inside main.py
# --host 0.0.0.0: listen on all network interfaces
# so that the container can send/receive data from anyone.
# allows the server to be accessible outside the container
# --port 8000: run appliaction on this port, which is common
# for web APIs
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]ssh -i Frontend Container
Copy project files, install dependencies, set up .env for API keys, and start the frontend
# node:20-alpine is small and resource efficient; it includes Node.js and Alpine Linux
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Docker Compose
Choose the ports
Build the frontend and backend containers
services:
backend:
build: ./backend
ports:
- "8000:8000"
env_file:
- ./backend/.env
volumes:
- ./backend:/app
- /app/venv # Exclude Python virtual env if created
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules # Exclude node_modules from volume mount
- /app/.next # Exclude Next.js build cache