Difficulty: Beginner | Time: 30 minutes | Focus: Container basics, health checks
Objectives
By working through this lab, you will gain hands-on experience with the entire container lifecycle. You will learn how to build a container image using a CleanStart base image, understanding how layers are assembled into a complete image. You will run a container and access its output, observing how containerized applications communicate with the outside world. You will implement a health check endpoint that allows container orchestration systems to monitor application health. You will verify container execution using Docker's log and inspection commands, developing diagnostic skills you'll use throughout your container journey. Finally, you will understand the fundamental Docker build and run workflow that underpins all containerized application development.
Prerequisites
Before beginning this lab, ensure your environment is properly configured. You will need Docker 20.10 or newer, a terminal (bash, zsh, or PowerShell depending on your operating system), a text editor (VS Code, nano, vim, or any editor you prefer), and internet access to pull the CleanStart Python 3.12 base image from the registry.
To verify that Docker is installed correctly, run the following command:
docker --versionThe output should indicate Docker version 20.10.0 or newer. If Docker is not installed on your system, follow the Docker installation guide appropriate for your operating system before proceeding.
Step 1: Create a Project Directory
Begin by creating a dedicated working directory for this lab. Open your terminal and navigate to your home directory, then create a new directory structure for your lab work:
mkdir -p ~/labs/lab-01-first-containercd ~/labs/lab-01-first-containerVerify that you are in the correct directory by checking your current working directory:
pwdYour terminal should output the full path to your lab directory:
/home/your-username/labs/lab-01-first-containerThis directory will serve as your workspace for all files related to this lab.
Step 2: Create a Simple Python Application
For this lab, you will create a simple Python HTTP server that will serve as your containerized application. This application will respond to health check requests and provide a status endpoint. Create a file called app.py in your lab directory:
cat > app.py << 'EOF'import osimport sysimport jsonfrom http.server import HTTPServer, BaseHTTPRequestHandlerfrom datetime import datetime class HealthCheckHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() response = { "status": "healthy", "timestamp": datetime.now().isoformat(), "version": "1.0.0", "service": "cleanstart-lab-01" } self.wfile.write(json.dumps(response).encode()) elif self.path == '/health': self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() response = {"status": "ok"} self.wfile.write(json.dumps(response).encode()) else: self.send_response(404) self.send_header('Content-type', 'application/json') self.end_headers() response = {"error": "Not found"} self.wfile.write(json.dumps(response).encode()) def log_message(self, format, *args): sys.stdout.write("[%s] %s\n" % (datetime.now().strftime('%Y-%m-%d %H:%M:%S'), format%args)) if __name__ == '__main__': port = int(os.getenv('PORT', '8000')) server = HTTPServer(('0.0.0.0', port), HealthCheckHandler) print(f"Starting server on port {port}") server.serve_forever()EOFVerify:
cat app.py | head -20Expected output:
import osimport sysimport jsonfrom http.server import HTTPServer, BaseHTTPRequestHandlerfrom datetime import datetime class HealthCheckHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() response = { "status": "healthy", "timestamp": datetime.now().isoformat(), "version": "1.0.0", "service": "cleanstart-lab-01" } self.wfile.write(json.dumps(response).encode())...Step 3: Create a Dockerfile
Create a file called Dockerfile:
cat > Dockerfile << 'EOF'FROM registry.cleanstart.com/cleanstart/python:3.12 # Set working directoryWORKDIR /app # Copy application codeCOPY app.py . # Expose the portEXPOSE 8000 # Set environment variable for portENV PORT=8000 # Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 # Run the applicationCMD ["python", "app.py"]EOFVerify:
cat DockerfileExpected output:
FROM registry.cleanstart.com/cleanstart/python:3.12...HEALTHCHECK --interval=30s......Step 4: Build the Container Image
Build the image:
docker build -t lab-01-app:latest .Expected output (abbreviated):
[+] Building 12.3s (6/6) FINISHED...[3/6] COPY app.py .[4/6] EXPOSE 8000[5/6] WORKDIR /app[6/6] RUN python -c "..."=> => naming to docker.io/library/lab-01-app:latestNote: First build may take 20-30 seconds as it pulls the base image. Subsequent builds are faster.
Step 5: Run the Container
Run the container:
docker run --name lab-01-container -d -p 8000:8000 lab-01-app:latestExpected output:
3f7a8b9c2d1e0f4a5b6c7d8e9f0a1b2c(The output is the container ID)
Step 6: Verify the Container is Running
Check container status:
docker psExpected output (your container ID will differ):
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3f7a8b9c2d1e lab-01-app:latest "python app.py" 5 seconds ago Up 4 seconds (healthy) 0.0.0.0:8000->8000/tcp lab-01-containerKey indicators: STATUS shows Up (running) and (healthy) (health check passed). PORTS shows 0.0.0.0:8000->8000/tcp (port mapping is active).
Step 7: Test the Application
Test the root endpoint:
curl http://localhost:8000/Expected output:
{"status": "healthy", "timestamp": "2024-03-22T14:32:45.123456", "version": "1.0.0", "service": "cleanstart-lab-01"}Test the health check endpoint:
curl http://localhost:8000/healthExpected output:
{"status": "ok"}Test a 404 endpoint:
curl http://localhost:8000/notfoundExpected output:
{"error": "Not found"}Step 8: View Container Logs
View the container logs:
docker logs lab-01-containerExpected output:
Starting server on port 8000[2024-03-22 14:32:45] 127.0.0.1 "GET / HTTP/1.1" 200 -[2024-03-22 14:32:48] 127.0.0.1 "GET /health HTTP/1.1" 200 -[2024-03-22 14:32:51] 127.0.0.1 "GET /notfound HTTP/1.1" 404 -Step 9: Inspect Image Details
Inspect the image to see its configuration:
docker inspect lab-01-app:latest | grep -A 20 '"Env"'Expected output (abbreviated):
"Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "PYTHON_VERSION=3.12", "PORT=8000"],Check the image size:
docker images | grep lab-01-appExpected output:
lab-01-app latest abcd1234efgh 2 minutes ago 123MBStep 10: Test Health Check Manually
The HEALTHCHECK directive runs automatically. Verify it's working by stopping the app (without killing the container) and watching the status change:
# Get into the container and stop the serverdocker exec lab-01-container kill -TERM 1Wait 10 seconds, then check status:
docker psExpected output (after ~30 seconds):
STATUS Up 1 minute (unhealthy)The container remains running but health check reports (unhealthy).
Verification Checklist
Before moving forward, confirm that all of the following have been completed successfully. The directory ~/labs/lab-01-first-container should have been created in your home directory. The app.py file should contain the HTTP server code with Flask routing and JSON response handling. The Dockerfile should use registry.cleanstart.com/cleanstart/python:3.12 as its base image. The docker build command should have completed successfully without errors. When you run docker ps, the container should be listed with a status showing (healthy) to indicate the health check is passing. Test the root endpoint with curl http://localhost:8000/ and verify it returns JSON with status "healthy". Test the health endpoint with curl http://localhost:8000/health and confirm it returns {"status": "ok"}. Review the docker logs output to verify the server has started and received the HTTP requests you made. Finally, use docker inspect to check that the PORT=8000 environment variable is set correctly in the image configuration.
If all of these items are verified, you have successfully completed Lab 01 and are ready to move on to more advanced topics.
What You Learned
Throughout this lab you gained foundational knowledge about container fundamentals. Base Images such as the CleanStart images published to registry.cleanstart.com provide secure, minimal foundations for building specialized container images. Dockerfiles define the complete build process for an image, specifying the base image, working directory, code to copy, ports to expose, health checks to implement, and the entry point for the application. The docker build -t name:tag . command creates a reproducible image from a Dockerfile, allowing consistent builds. Container Execution through docker run -d -p 8000:8000 image:tag starts a container in the background with port mapping to expose container ports to the host. The HEALTHCHECK directive monitors container health and this status is reflected directly in docker ps output, providing visibility into application health. Port Mapping using the -p 8000:8000 flag exposes the container's internal port 8000 to your host machine so you can access the service. Logging with docker logs displays all stdout and stderr output from inside the container, essential for debugging. Finally, Image Inspection via docker inspect reveals the complete image configuration including environment variables, exposed ports, and other metadata.
Cleanup
Stop and remove the container:
docker stop lab-01-containerdocker rm lab-01-containerRemove the image:
docker rmi lab-01-app:latestRemove the lab directory (optional):
rm -rf ~/labs/lab-01-first-containerNext Lab
Proceed to Lab 02: Multi-Stage Builds to learn how to optimize image size and improve security through build patterns.
Troubleshooting
Q: "Cannot connect to Docker daemon" A: Start Docker. On Windows/macOS, open Docker Desktop. On Linux, run sudo systemctl start docker or sudo service docker start.
Q: "image registry.cleanstart.com/cleanstart/python:3.12 not found" A: Check your internet connection and ensure you can access registry.cleanstart.com. Try docker pull registry.cleanstart.com/cleanstart/python:3.12 directly.
Q: "Address already in use [::]:8000" A: Port 8000 is already in use. Either stop the other service, or use a different port: docker run -p 9000:8000 ... (then curl http://localhost:9000/).
Q: "curl: (7) Failed to connect to localhost port 8000" A: Ensure the container is running (docker ps), and verify port mapping with docker inspect lab-01-container | grep -A 5 PortBindings.
Estimated Time: 30 minutes | Hands-on: ~25 minutes | Reading: ~5 minutes
