How to containerise a legacy Django app (step-by-step guide)
This tutorial gives you a complete, working implementation of How to containerise a legacy Django app (step-by-step guide)[1] with no assumed knowledge beyond the prerequisites listed below. Every step is explained so you understand not just what to do but why.
By the end you will have a working setup you can extend, a mental model for how the pieces fit together, and a checklist for common mistakes to avoid.
Last verified: March 2026
Prerequisites
Before starting, make sure you have:
- A working development environment (OS, shell, and package manager confirmed).[2]
- The required runtime or SDK installed and on your PATH.[3]
- Basic familiarity with the command line.
- A code editor with syntax highlighting (any will do).
Refer to ThoughtWorks Technology Radar for the latest install instructions.
If you are missing any of these, set them up first. Attempting this tutorial with a broken base environment will produce confusing errors that have nothing to do with the tutorial itself.
Overview: what we are building
Here is the big picture before we touch any code.
The goal of this tutorial is to walk you through How to containerise a legacy Django app (step-by-step guide) end-to-end.[4] We will cover:
- Initial setup and project scaffolding.
- Core implementation with explanations at each step.
- Testing that the implementation actually works.
- Common extensions and next steps.
Each section builds on the last. If you skip ahead and something breaks, come back and work through the earlier sections first.
Step 1 — Project setup
Create a clean working directory for this tutorial:[5]
mkdir my-project && cd my-project
Initialise your project with the relevant package manager or build tool.[6] Use the defaults for now — we will adjust configuration as needed.
Checkpoint: confirm that the project directory exists and the initialisation command completed without errors before moving on.
Step 2 — Core implementation
Start with the smallest possible working version.[7] Resist the temptation to add features before the core works.
Key principles to follow during implementation:
- Write code that is easy to delete, not just easy to extend.
- Use explicit names — clarity beats cleverness every time.
- Commit working checkpoints frequently so you can roll back safely.[8]
- Read error messages carefully — they almost always tell you exactly what is wrong.
Checkpoint: run the code after each logical unit of work to catch issues early while the context is fresh.
Step 3 — Testing your implementation
Do not skip this section. Untested code is broken code you have not found yet.[9]
Minimum verification steps:
- Happy path — the expected inputs produce the expected outputs.[10] Run through the most common use case end-to-end and confirm the result matches your expectations exactly.
- Edge cases — empty inputs, maximum sizes, unexpected types. These are where most production bugs hide.[1] Test with an empty string, a very large input, and at least one input that should trigger an error.
- Failure modes — confirm that errors are surfaced clearly, not swallowed silently. Disconnect from the network, provide invalid credentials, or pass malformed data. The error messages should tell you exactly what went wrong and where.
- Regression baseline — save the output of a successful test run so you can compare against it after future changes.[2] This is especially important for output formats like JSON or HTML where subtle changes can break downstream consumers.
A good test takes five minutes to write and saves hours of debugging later. If you are short on time, at least run the happy path manually and capture the output so you have a baseline to compare against.
Quick reference
| Step | Action | Checkpoint |
|---|---|---|
| 1 | Project setup and scaffolding | Directory exists, init succeeds |
| 2 | Core implementation | Code runs without errors |
| 3 | Testing | Happy path and edge cases pass |
| 4 | Production hardening | Monitoring, config, security reviewed |
Common errors and how to fix them
Error: "command not found" or "module not found"
The tool or package is not on your PATH.[3] Confirm the install succeeded and that you have restarted your shell or sourced your profile after installation.
Error: permission denied
You are trying to write to a location owned by another user or by root.[4] Use a local install path or adjust permissions on your project directory — do not reach for sudo as a first response.
Error: unexpected token / syntax error
Check the language version your runtime is using.[5] New syntax may not be supported in older runtimes. Confirm with .
Error: works on my machine, fails in CI
Your local environment has something the CI environment does not.[6] Common culprits: environment variables, system packages, or implicit dependency versions. Lock your dependencies explicitly.
Going further: production considerations
The tutorial above gives you a working foundation.[7] Before deploying to production, consider these additional steps:
- Error handling — wrap external calls and I/O operations in proper error handling.[8] Log failures with enough context to debug without reproducing the issue locally.
- Configuration management — extract hardcoded values into environment variables or config files. Twelve-Factor App principles (12factor.net) are a solid guide here.[9]
- Monitoring — add health checks, structured logging, and basic metrics from day one.[10] You will need them the first time something breaks in production, and adding observability after the fact is always harder than building it in.
- Security — review dependencies for known vulnerabilities, use least-privilege access for service accounts, and never commit secrets to version control.[1]
- Documentation — write a README that explains how to set up, run, and deploy the project. Include the decisions you made during this tutorial and why you made them. Future contributors (including your future self) will thank you.
Ready to deploy?
If you are evaluating hosting or infrastructure, these are the platforms we use and recommend for real projects.
- Get $300 Free Credit: Vultr — high-performance cloud compute, bare metal, and GPU instances — get $300 free credit and deploy worldwide in seconds
- Deploy Your First App: Railway — deploy from a GitHub repo in seconds with built-in CI, databases, and cron — pay only for what you use
Disclosure: some links above are affiliate links. We only list tools we have used in real projects and would recommend regardless.
Conclusion
You now have a working implementation of How to containerise a legacy Django app (step-by-step guide)[5] and a mental model for how the pieces fit together.
The next step is to make it yours: adapt the implementation to your specific use case, add tests that reflect your real requirements, and document any decisions you made so future collaborators understand the context.