Lock Down Python Docker Containers
Practical steps to secure your Python Docker images and containers: minimal base images, non-root users, pinned dependencies, scanning, resource limits, and read-only filesystems.
Secure Python Docker Containers from Exploits
You've probably heard it a thousand times: "It works on my machine." Docker was supposed to fix that, but if you're not careful, your containerized Python app might become a playground for attackers. Let's talk about real, practical ways to lock things down without going crazy.
Start with a Minimal Base Image
Most people grab python:3.11 and call it a day. That image is over 900MB. That's not just disk space—that's a massive attack surface. Every included package, library, and tool is a potential entry point.
Use python:3.11-slim or even python:3.11-alpine instead. The slim version is about 120MB. Alpine is around 50MB. Fewer packages mean fewer vulnerabilities.
FROM python:3.11-slim-bookworm
But here's the thing: alpine uses musl libc instead of glibc. Some Python packages don't play nice with it. Test first.
Never Run as Root
Docker containers run as root by default. If an attacker gets shell access to your container, they have root. That's a gift basket for bad actors.
Create a non-root user in your Dockerfile:
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
Put this after you install dependencies, but before the CMD. That way, you don't have permission issues during the build.
Pin Dependencies, Seriously
"PythonSkillset had a nasty wake-up call last year when an automated rebuild pulled in a compromised version of a logging library. It took hours to trace."
Lock your dependencies with exact versions:
requests==2.31.0
flask==3.0.0
Don't use >= or ~=. Use pip freeze > requirements.txt after testing. Even better, use a lock file with pip-tools or poetry. Every time you rebuild, you get the same bits.
Scan Before You Deploy
There's no excuse for not scanning images anymore. Tools like Trivy, Snyk, or Grype catch known vulnerabilities in base images and dependencies.
trivy image your-python-app:latest
Make this part of your CI pipeline, not something you run once a month. When a high-severity CVE drops for numpy, you want to know immediately.
Remove Unused Tools
Your Python app probably doesn't need curl, wget, git, or a compiler. Every one of those is a weapon waiting to be used.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& pip install -r requirements.txt \
&& apt-get purge -y build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
Yes, it's longer. Yes, it's worth it. The smaller the container, the harder it is to break.
Set Resource Limits
Docker containers can consume all your host's memory and CPU if you don't constrain them. That's not just a performance issue—it's a denial-of-service vulnerability.
docker run --memory="512m" --cpus="1.0" your-python-app
Or set it in docker-compose:
services:
app:
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
Use Read-Only Filesystems
Most Python apps don't need to write to the filesystem at runtime. Tell Docker to make it read-only:
docker run --read-only your-python-app
If your app needs to write to a specific directory (like /tmp or /var/log), mount a tmpfs:
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid,size=100m your-python-app
Now if an attacker tries to write malicious code to disk, they can't.
Handle Secrets Properly
Do not bake secrets into your image. No .env files, no hardcoded API keys, no passwords in Dockerfiles. Use Docker secrets or environment variables passed at runtime.
docker run -e DB_PASSWORD=$(cat /run/secrets/db_password) your-python-app
Or in compose:
secrets:
db_password:
file: ./db_password.txt
Don't Expose Unnecessary Ports
Only expose the port your app actually needs. If you're running a Flask app on port 5000, don't also expose debug ports or management interfaces.
EXPOSE 5000
And when you run it, map only what's necessary:
docker run -p 5000:5000 your-python-app
Watch Your Logging
Python's print() statements go to stdout by default, which Docker captures. That's fine. But if you're logging sensitive data—passwords, tokens, PII—you're putting it at risk.
Use structured logging with a library like structlog or the built-in logging module. Filter out sensitive fields before they hit the logs.
import logging
logger = logging.getLogger(__name__)
logger.info("User login successful", extra={"user_id": user.id, "ip": anonymize(ip)})
Test Your Dockerfile
A quick sanity check: run docker scout quickview on your image. Or use docker run --rm your-python-app sh -c "whoami". If it says root, fix that.
The Bottom Line
Securing Docker containers isn't about doing one big thing right. It's about doing a hundred small things right. Minimal base, non-root user, pinned dependencies, scanning, resource limits, read-only filesystem.
PythonSkillset learned some of these lessons the hard way. You don't have to. Start with the basics, automate the checks, and your containers will be harder to crack.
Got a specific security concern with your Python Docker setup? The community over at PythonSkillset.com has seen it all.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.