Intermediate

Developing Python 3 Apps with Docker

Containerize Python apps, run multiple containers with Compose, make them production-ready and debug.

Demo project: Wired Brain Coffee — product e-commerce service using Python/Docker microservices


Table of Contents


Module 1 — Course Overview

This course covers developing Python applications in Docker containers, from the basics to production deployment. The main topics covered:

TopicDescription
DockerizingCreating a Dockerfile and building a Python image
Docker ComposeOrchestrating multiple containers for a single application
Production-readyLogging, external configuration, volumes, secrets, networks
DebuggingRemote debugging with PyCharm and Visual Studio Code

Module 2 — Getting Started with Python and Docker

Basic Architecture

graph TD
    DEV[Developer / IDE]
    DF[Dockerfile]
    IMG[Docker Image]
    CTR[Docker Container]
    HUB[Docker Hub / Registry]

    DEV -->|writes| DF
    DF -->|docker build| IMG
    IMG -->|docker run| CTR
    IMG -->|docker push| HUB
    HUB -->|docker pull| IMG

    style DF fill:#f0ad4e,color:#000
    style IMG fill:#5bc0de,color:#000
    style CTR fill:#5cb85c,color:#fff

Setting Up the Environment

The Wired Brain Coffee project uses a microservices architecture. The wired-brain/ root directory contains all services.

Project initialization from the command line:

# Create the directory structure
mkdir wired-brain
cd wired-brain
mkdir product-service
cd product-service

# Create a virtual environment
python -m venv venv

# Activate the virtual environment
# Mac / Linux
source venv/bin/activate
# Windows
venv\Scripts\activate.bat

# Install Flask
pip install flask

# Save dependencies
pip freeze > requirements.txt

Project structure:

wired-brain/
├── product-service/
│   ├── src/
│   │   ├── app.py
│   │   ├── db.py
│   │   └── product.py
│   ├── config/
│   │   ├── db.ini
│   │   └── logging.ini
│   ├── Dockerfile
│   └── requirements.txt
├── nginx/
│   ├── Dockerfile
│   └── nginx.conf
└── docker-compose.yml

Visual Studio Code vs. PyCharm

CriterionVisual Studio CodePyCharm
LicenseOpen source, freeCommercial (Community/Pro editions)
Runtime languageJavaScript / TypeScript (Electron)Java (JVM)
Memory footprintLowHigher
Python supportVia extension (Python, Pylance)Native
Docker supportVia Docker extensionNative
TypeAdvanced code editorFull IDE
Remote debuggingdebugpy (remote attach)pydevd-pycharm (connect to IDE)

Building a Flask Application

Flask is a Python micro web framework: it handles HTTP requests without imposing an ORM, form validation, etc. It follows an optional extensions model.

src/app.py — First iteration with in-memory list:

from flask import Flask, jsonify, request

app = Flask(__name__)

# In-memory data (before database integration)
catalog_items = [
    {"id": 1, "name": "Wired Brain Coffee Mug"},
    {"id": 2, "name": "Wired Brain Coffee T-Shirt"}
]

@app.route("/products", methods=["GET"])
def get_products():
    return jsonify(catalog_items), 200

@app.route("/product/<int:product_id>", methods=["GET"])
def get_product(product_id):
    product = next((p for p in catalog_items if p["id"] == product_id), None)
    if product is None:
        return jsonify({"error": "Product not found"}), 404
    return jsonify(product), 200

@app.route("/product", methods=["POST"])
def create_product():
    data = request.get_json()
    new_id = max(p["id"] for p in catalog_items) + 1
    product = {"id": new_id, "name": data["name"]}
    catalog_items.append(product)
    return jsonify(product), 201

@app.route("/product/<int:product_id>", methods=["PUT"])
def update_product(product_id):
    product = next((p for p in catalog_items if p["id"] == product_id), None)
    if product is None:
        return jsonify({"error": "Product not found"}), 404
    data = request.get_json()
    product["name"] = data["name"]
    return jsonify(product), 200

@app.route("/product/<int:product_id>", methods=["DELETE"])
def delete_product(product_id):
    global catalog_items
    product = next((p for p in catalog_items if p["id"] == product_id), None)
    if product is None:
        return jsonify({"error": "Product not found"}), 404
    catalog_items = [p for p in catalog_items if p["id"] != product_id]
    return jsonify(product), 200

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)

Testing the API with curl:

# GET - list all products
curl http://localhost:5000/products

# GET - retrieve a product by ID
curl -v http://localhost:5000/product/1

# POST - create a product
curl --header "Content-Type: application/json" \
     --request POST \
     --data '{"name": "New Item"}' \
     -v http://localhost:5000/product

# PUT - update a product
curl --header "Content-Type: application/json" \
     --request PUT \
     --data '{"name": "Updated Item"}' \
     -v http://localhost:5000/product/1

# DELETE - remove a product
curl --request DELETE http://localhost:5000/product/1

Dockerizing a Flask Application

A Dockerfile is a text document containing all the commands to assemble an image. The lifecycle is:

  1. Write a Dockerfile
  2. Build a Docker image (docker build)
  3. Start a Docker container (docker run)

Key concept — layers: Each instruction in the Dockerfile creates a layer. Docker reuses cached layers if nothing has changed. Therefore, place least volatile items first (dependencies) and most volatile items last (source code).

Creating a Dockerfile and Starting a Container

product-service/Dockerfile:

# Official Python base image
FROM python:3.9

# Set the working directory inside the container
WORKDIR /code

# Copy dependencies FIRST (stable layer = optimal caching)
COPY requirements.txt .

# Install Python dependencies
RUN pip install -r requirements.txt

# Copy source code LAST (volatile layer)
COPY src/ .

# Start the Flask application
CMD ["python", "./app.py"]

requirements.txt:

Flask

Basic Docker commands:

# Build the image from the Dockerfile
docker build -t wired-brain/product-service:1.0 .

# List local images
docker images

# Start a container in detached mode with port mapping
docker run -d -p 5000:5000 --name product-service wired-brain/product-service:1.0

# View active containers
docker ps

# View container logs
docker logs product-service

# Stop and remove a container
docker stop product-service
docker rm product-service

Module 3 — Running Multiple Containers with Docker Compose

Multi-Container Architecture

graph LR
    CLIENT[HTTP Client]
    NGINX[Nginx :80\nreverse proxy]
    PS[product-service :5000\nFlask + SQLAlchemy]
    DB[(MySQL :3306\ndatabase)]

    CLIENT -->|HTTP :80| NGINX
    NGINX -->|proxy_pass :5000| PS
    PS -->|SQL| DB

    subgraph "Docker Compose — private network wired-brain"
        NGINX
        PS
        DB
    end

    style NGINX fill:#009639,color:#fff
    style PS fill:#3776ab,color:#fff
    style DB fill:#4479a1,color:#fff

Introduction to Docker Compose

Docker Compose is a tool for defining and starting multi-container applications. A single docker-compose.yml file describes all services.

Advantages of Docker Compose:

  • Create multiple isolated environments on a single host
  • Start / stop the entire application with a single command
  • Automatic private network between containers (DNS resolution by service name)
  • docker-compose build to build all services
  • docker-compose up -d to start everything in detached mode
  • docker-compose down to stop and remove containers

Configuring Docker Compose for the Product Service

docker-compose.yml — Initial version:

version: "3.8"

services:
  productservice:
    build: ./product-service
    ports:
      - "5000:5000"

Docker Compose commands:

# Navigate to wired-brain/
cd wired-brain

# Build all services
docker-compose build

# Start all containers in detached mode
docker-compose up -d

# View active containers
docker ps

# View logs for a service
docker-compose logs productservice

# Stop and remove containers + network
docker-compose down

Adding Nginx as a Reverse Proxy

A reverse proxy receives client requests and redirects them to the appropriate backend. Nginx intercepts requests on port 80 and forwards them to productservice on port 5000.

nginx/nginx.conf:

server {
    listen 80;

    location / {
        proxy_pass http://productservice:5000;
    }
}

Note: productservice is resolved by Docker Compose’s internal DNS — no IP address needed.

nginx/Dockerfile:

FROM nginx
COPY nginx.conf /etc/nginx/conf.d/default.conf

docker-compose.yml — With Nginx:

version: "3.8"

services:
  web:
    build: ./nginx
    ports:
      - "80:80"

  productservice:
    build: ./product-service
    # No need to expose port 5000 on the host machine
    # All traffic goes through Nginx

Introduction to SQLAlchemy (ORM)

SQLAlchemy is a SQL toolkit and ORM (Object-Relational Mapper) for Python. The project uses Flask-SQLAlchemy to simplify integration with Flask.

Why use an ORM?

SituationWithout ORMWith SQLAlchemy
Simple relationship (1 table)Manual SQL acceptableSimplified
Many-to-many relationshipComplex join tablesHandled automatically
Database portabilityVendor-specific queriesAbstraction via dialects

3 steps to use SQLAlchemy:

  1. Create the SQLAlchemy object
  2. Initialize the Flask application with this object
  3. Create persistence classes (models)

src/db.py:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

Integrating MySQL with SQLAlchemy

src/product.py — SQLAlchemy model:

from db import db

class Product(db.Model):
    __tablename__ = "products"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)

    def json(self):
        return {"id": self.id, "name": self.name}

    @classmethod
    def find_all(cls):
        return cls.query.all()

    @classmethod
    def find_by_id(cls, product_id):
        return cls.query.filter_by(id=product_id).first()

    def save_to_db(self):
        db.session.add(self)
        db.session.commit()

    def delete_from_db(self):
        db.session.delete(self)
        db.session.commit()

src/app.py — With SQLAlchemy:

from flask import Flask, jsonify, request
from db import db
from product import Product

app = Flask(__name__)

# MySQL connection URI: mysql://user:password@host/database
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:devpass@db/catalog"

# Initialize SQLAlchemy with the Flask application
db.init_app(app)

@app.before_first_request
def create_tables():
    db.create_all()

@app.route("/products", methods=["GET"])
def get_products():
    return jsonify([p.json() for p in Product.find_all()]), 200

@app.route("/product/<int:product_id>", methods=["GET"])
def get_product(product_id):
    product = Product.find_by_id(product_id)
    if product is None:
        return jsonify({"error": "Product not found"}), 404
    return jsonify(product.json()), 200

@app.route("/product", methods=["POST"])
def create_product():
    data = request.get_json()
    product = Product(name=data["name"])
    product.save_to_db()
    return jsonify(product.json()), 201

@app.route("/product/<int:product_id>", methods=["DELETE"])
def delete_product(product_id):
    product = Product.find_by_id(product_id)
    if product is None:
        return jsonify({"error": "Product not found"}), 404
    product.delete_from_db()
    return jsonify(product.json()), 200

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)

requirements.txt:

Flask
Flask-SQLAlchemy
flask_mysqldb
# SQLAlchemy 1.4+ compatibility workaround
SQLAlchemy<1.4

MySQL initialization script — product-service/init.sql:

CREATE DATABASE IF NOT EXISTS catalog;
USE catalog;

CREATE TABLE IF NOT EXISTS products (
    id   INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

docker-compose.yml — With MySQL:

version: "3.8"

services:
  web:
    build: ./nginx
    ports:
      - "80:80"

  productservice:
    build: ./product-service
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: devpass
      MYSQL_DATABASE: catalog
    volumes:
      - ./product-service/init.sql:/docker-entrypoint-initdb.d/init.sql

Testing the Application with Postman

Postman is an API client that allows you to send HTTP requests, inspect responses, and write validation tests.

Postman Collection — Product Service:

RequestMethodURLBody
GET /productsGEThttp://localhost/products
GET /product/:idGEThttp://localhost/product/1
POST /productPOSThttp://localhost/product{"name": "New Product"}
DELETE /product/:idDELETEhttp://localhost/product/1

Module 4 — Making Your Application Production-ready

Twelve-Factor Methodology

The Twelve-Factor App is a methodology for building robust SaaS applications.

mindmap
  root((Twelve-Factor))
    Codebase
      One repo, multiple deployments
    Dependencies
      Explicitly declared
    Config
      Externalized in environment
    Backing Services
      Treated as attached resources
    Build/Release/Run
      Strictly separated stages
    Processes
      Stateless processes
    Port Binding
      Services exported via port
    Concurrency
      Scale via process model
    Disposability
      Fast startup, graceful shutdown
    Dev/Prod Parity
      Dev and prod as similar as possible
    Logs
      Treated as event streams
    Admin Processes
      Isolated one-off tasks

The 4 most relevant factors with Docker:

FactorDocker contribution
ConfigEnvironment variables, secrets, config files mounted as volumes
Dev/Prod ParitySame image in dev and prod
Logsstdout/stderr collected by Docker
DisposabilityContainers started / stopped quickly

Python Logging Module

Python’s logging module is part of the standard library. All Python modules can participate in logging, including third-party libraries.

Log levels:

LevelNumeric valueUsage
CRITICAL50Fatal error, application unrecoverable
ERROR40Recoverable error
WARNING30Warning, unexpected behavior
INFO20General information
DEBUG10Detailed debugging information

3 components of the logging module:

  1. Loggers — Hierarchy (root logger → child loggers)
  2. Handlers — Destinations (console, file, network)
  3. Formatters — Message format (timestamp, level, logger, message)

config/logging.ini:

[loggers]
keys=root,app

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=INFO
handlers=consoleHandler

[logger_app]
level=DEBUG
handlers=consoleHandler
qualname=app
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(levelname)s - %(name)s - %(message)s

src/app.py — With logging:

import logging
import logging.config
from flask import Flask, jsonify, request
from db import db
from product import Product

# Load logger configuration from ini file
logging.config.fileConfig("config/logging.ini")
logger = logging.getLogger(__name__)

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:devpass@db/catalog"
db.init_app(app)

@app.route("/products", methods=["GET"])
def get_products():
    logger.debug("GET /products called")
    try:
        return jsonify([p.json() for p in Product.find_all()]), 200
    except Exception as e:
        logger.exception("Error fetching products")
        return jsonify({"error": "Internal Server Error"}), 500

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)

Application Configuration with ConfigParser

The configparser module reads configuration files in INI format.

config/db.ini:

[mysql]
host = db
username = root
password = devpass
database = catalog

src/app.py — Reading the configuration:

import configparser

def get_database_url():
    config = configparser.ConfigParser()
    config.read("config/db.ini")
    mysql = config["mysql"]
    return f"mysql://{mysql['username']}:{mysql['password']}@{mysql['host']}/{mysql['database']}"

app.config["SQLALCHEMY_DATABASE_URI"] = get_database_url()

Docker Volumes

graph TD
    subgraph "Docker Volume Types"
        AV[Anonymous Volume\nDocker generates a random name]
        HV[Host Volume\nMounts a local directory into the container]
        NV[Named Volume\nDefined name, managed by Docker, persistent]
    end

    style AV fill:#f0ad4e,color:#000
    style HV fill:#5bc0de,color:#000
    style NV fill:#5cb85c,color:#fff
Typedocker-compose.yml syntaxTypical usage
Anonymous- /var/lib/mysqlTemporary data
Host- ./src:/codeDev live reload, config files
Named- db_data:/var/lib/mysqlDB data persistence

docker-compose.yml — With volumes:

version: "3.8"

services:
  productservice:
    build: ./product-service
    volumes:
      # Host volume: mount config from the host machine
      - ./product-service/config:/config
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: devpass
    volumes:
      # Named volume: data persisted between restarts
      - db_data:/var/lib/mysql
      # Host volume: init script
      - ./product-service/init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db_data:

Docker Secrets

A Docker secret is a blob of sensitive data (passwords, private keys, certificates) managed securely.

ModeEncryptionAvailability
File-based (Docker Compose)No — plaintext fileLocal only
Encrypted external (Docker Swarm)Yes — encrypted at restSwarm cluster

db_password.txt:

mysecurepassword

docker-compose.yml — With secrets:

version: "3.8"

services:
  productservice:
    build: ./product-service
    secrets:
      - db_password
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./db_password.txt

Reading the secret in the Python application:

def get_db_password():
    try:
        with open("/run/secrets/db_password", "r") as f:
            return f.read().strip()
    except FileNotFoundError:
        # Fallback for local development
        return "fallback_dev_password"

Docker Compose Networks

By default, all containers in a docker-compose.yml share a single network — a compromised container could access all others. Network segmentation is a security best practice.

graph LR
    subgraph "frontend network"
        NGINX[Nginx\nweb]
        PS[product-service]
    end
    subgraph "backend network"
        PS2[product-service]
        DB[(MySQL\ndb)]
    end

    CLIENT[Client] -->|:80| NGINX
    NGINX --> PS
    PS -.->|same service| PS2
    PS2 --> DB

    style NGINX fill:#009639,color:#fff
    style PS fill:#3776ab,color:#fff
    style PS2 fill:#3776ab,color:#fff
    style DB fill:#4479a1,color:#fff

Communication rules:

ContainerCan communicate with
web (Nginx)productservice only
productserviceweb AND db
db (MySQL)productservice only

docker-compose.yml — With segmented networks:

version: "3.8"

services:
  web:
    build: ./nginx
    ports:
      - "80:80"
    networks:
      - frontend

  productservice:
    build: ./product-service
    secrets:
      - db_password
    volumes:
      - ./product-service/config:/config
    networks:
      - frontend
      - backend
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - db_data:/var/lib/mysql
      - ./product-service/init.sql:/docker-entrypoint-initdb.d/init.sql
    secrets:
      - db_password
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  db_data:

secrets:
  db_password:
    file: ./db_password.txt

Module 5 — Debugging Python Applications Running in Containers

Modifying Python Code in a Container

Without optimization, the development cycle is: modify → stop → rebuild → restart. This can be eliminated by combining:

  1. Flask in debug mode (debug=True): automatic reload on every file modification
  2. Host volume: mount the local source code directly into the container
# Add to the productservice service
volumes:
  - ./product-service/src:/code  # Live reload of source code
  - ./product-service/config:/config

Result: modify app.py in the IDE → Flask automatically reloads in the container — without rebuilding.

Debugging Architecture

sequenceDiagram
    participant IDE as IDE (PyCharm / VS Code)
    participant CTR as Docker Container
    participant APP as Flask App

    Note over IDE,CTR: PyCharm Mode (push)
    IDE->>IDE: Start debug server
    CTR->>IDE: pydevd_pycharm.settrace() → incoming connection
    IDE-->>CTR: Breakpoints active

    Note over IDE,CTR: VS Code Mode (pull)
    CTR->>CTR: debugpy.listen(0.0.0.0:5678)
    IDE->>CTR: Remote Attach → outgoing connection
    IDE-->>CTR: Breakpoints active

Debugging with PyCharm

The PyCharm model is reversed: it is the container that connects to PyCharm (not the other way around).

requirements.txt — Add PyCharm debugpy:

pydevd-pycharm~=213.6461.77

src/app.py — With PyCharm debugger:

import pydevd_pycharm

# host.docker.internal → IP of the host machine from a Docker container
pydevd_pycharm.settrace(
    "host.docker.internal",
    port=12345,
    stdoutToServer=True,
    stderrToServer=True
)

Steps in PyCharm:

  1. Run → Edit Configurations → Add → Python Remote Debug
  2. Set the port (e.g.: 12345)
  3. Start the debug server in PyCharm
  4. Start the containers → the container connects to PyCharm
  5. Set breakpoints, step through the code

Debugging with Visual Studio Code

The VS Code model is standard: VS Code connects to the container that is listening.

requirements.txt — Add debugpy:

debugpy

src/app.py — With debugpy (VS Code):

import debugpy

# Listen for debugger connections from outside the container
debugpy.listen(("0.0.0.0", 5678))

# Optional: wait for a debugger to connect before continuing
# debugpy.wait_for_client()

Expose the debug port in docker-compose.yml:

productservice:
  build: ./product-service
  ports:
    - "5678:5678"  # debugpy port
  volumes:
    - ./product-service/src:/code

.vscode/launch.json — Remote Attach configuration:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Remote Attach",
      "type": "python",
      "request": "attach",
      "connect": {
        "host": "localhost",
        "port": 5678
      },
      "pathMappings": [
        {
          "localRoot": "${workspaceFolder}/product-service/src",
          "remoteRoot": "/code"
        }
      ]
    }
  ]
}

Steps in VS Code:

  1. Start the containers with docker-compose up -d
  2. Go to Run and Debug (Ctrl+Shift+D)
  3. Select Python: Remote Attach and press ▶
  4. VS Code connects to the container on port 5678
  5. Set breakpoints in the local source code

Reference Tables

Official Python Images on Docker Hub

ImageDescriptionSize (approx.)
python:3.xFull Debian image~900 MB
python:3.x-slimMinimal Debian~150 MB
python:3.x-alpineAlpine Linux (very lightweight)~50 MB
python:3.x-slim-busterMinimal Debian Buster~120 MB

Python Dockerfile Optimizations

TechniqueDescriptionImpact
Layer orderingDependencies before source codeBetter cache during rebuilds
.dockerignoreExclude venv/, __pycache__/, .git/Lighter image
slim/alpine imageUse python:3.x-slim~80% size reduction
--no-cache-dirpip install --no-cache-dirAvoids storing pip cache in image
COPY in one passCOPY src/ . rather than file by fileFewer layers
Multi-stage buildSeparate build and runtimeFinal image without build tools

Multi-Stage Build Python

graph LR
    subgraph "Stage 1 — Builder"
        B1[python:3.9 base]
        B2[pip install deps]
        B3[Compiled code / wheels]
    end
    subgraph "Stage 2 — Runner"
        R1[python:3.9-slim base]
        R2[COPY --from=builder]
        R3[Lightweight final image]
    end
    B3 -->|COPY --from=builder| R2
    style B1 fill:#f0ad4e,color:#000
    style R1 fill:#5cb85c,color:#fff

Multi-stage Dockerfile example:

# Stage 1: builder — installs dependencies
FROM python:3.9 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# Stage 2: runner — lightweight image without build tools
FROM python:3.9-slim
WORKDIR /code

# Copy only the installed packages
COPY --from=builder /root/.local /root/.local
COPY src/ .

ENV PATH=/root/.local/bin:$PATH

CMD ["python", "./app.py"]

Debugging Mode Comparison

AspectPyCharm (pydevd-pycharm)VS Code (debugpy)
Connection directionContainer → IDEIDE → Container
StartupIDE starts debug server firstContainer starts debug server first
PortConfigurable (e.g.: 12345)5678 (convention)
Host hostnamehost.docker.internallocalhost (with exposed port)
Wait for clientOn container startupdebugpy.wait_for_client() (optional)

Summary of Essential Docker Commands

# === Images ===
docker build -t image-name:tag .          # Build an image
docker images                             # List local images
docker pull python:3.9-slim               # Download an image from Docker Hub
docker rmi image-name:tag                 # Remove an image

# === Containers ===
docker run -d -p 5000:5000 --name my-container image-name   # Start a container
docker ps                                 # List active containers
docker ps -a                              # All containers (including stopped)
docker stop my-container                  # Stop a container
docker rm my-container                    # Remove a container
docker logs my-container                  # View logs
docker exec -it my-container bash         # Interactive shell in a container

# === Docker Compose ===
docker-compose build                      # Build all services
docker-compose up -d                      # Start all containers
docker-compose down                       # Stop and remove containers + network
docker-compose logs service-name          # Logs for a specific service
docker-compose ps                         # Status of Compose containers

# === Volumes ===
docker volume ls                          # List volumes
docker volume rm volume-name              # Remove a volume

# === Networks ===
docker network ls                         # List networks
docker network inspect network-name       # Inspect a network

Search Terms

developing · python · apps · docker · containerization · containers · kubernetes · application · debugging · compose · architecture · container · dockerfile · flask · pycharm · running · sqlalchemy · studio · visual

Interested in this course?

Contact us to book it or get a custom training plan for your team.