AI News Hub Logo

AI News Hub

🚀 jenkins pipeline for django — common mistakes and how to fix them

DEV Community
Python-T Point

“Automation doesn’t eliminate work — it eliminates stupid work.” I remember the time I broke a production deploy because I forgot to run migrate. Not collectstatic — that would’ve been bad enough. No. I let new models hit the DB while the old code was still live. Stack trace had something like django.core.exceptions.FieldError: Unknown field 'updated_at'. 2 AM. Cold chai. A hotel chain losing bookings by the minute. Yeah, I learned this the hard way. Look — we’ve all been there. At that point, I was doing Friday night deploys like some kind of cursed ritual. git push, ssh in, source venv, python manage.py test — fingers crossed. Then collectstatic, then restart gunicorn, then pray nginx doesn’t throw 502s. Spoiler: it usually did. And then I set up my first jenkins pipeline for django. Not glamorous. Not fancy. But it worked. Like, actually worked. No more all-nighters. No more “did I forget something?” panic. Now? Deploys are quieter than my Sunday mornings with filter coffee. Routine. Predictable. Boring — in the best way. Let’s talk about how you can get there too. Need Here’s the thing — Jenkins won’t fix a messy project. If your Django settings are hardcoded, if you’re committing secrets.py, if your requirements.txt is out of date… stop. Fix that first. Trust me, I’ve seen pipelines fail because someone used pip freeze > reqs.txt in 2019 and never touched it again. So — before Jenkins, get your ducks in a row: A Django app on GitHub or another Git provider An EC2 instance running Ubuntu (or Amazon Linux) Jenkins installed and running on that instance Basic CI/CD understanding — stages, agents, scripts Python , pip , and virtualenv set up on the Jenkins server And — this one’s critical — your Django settings must be environment-aware. I use django-environ now. Used to use python-decouple. Same idea. # settings.py from decouple import config SECRET_KEY = config('SECRET_KEY') DEBUG = config('DEBUG', default=False, cast=bool) Why? Because Jenkins is going to inject those values at runtime. If your app crashes when DEBUG=False, better find out in CI than when the CEO is demoing it. Not gonna lie — I once spent three hours debugging why tests passed locally but failed on Jenkins. Turns out, DEBUG=True was hiding a missing STATIC_ROOT. Duh. Pieces Together Alright. Ubuntu box. SSH access. Let’s install Jenkins. Quick note: Jenkins needs Java. I know. (Java. In 2024. sigh) But it is what it is. sudo apt update sudo apt install openjdk-11-jdk -y wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add - sudo sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list' sudo apt update sudo apt install jenkins -y sudo systemctl enable jenkins sudo systemctl start jenkins Now go to http://your-ec2-public-ip:8080. Initial setup. Admin password from /var/lib/jenkins/secrets/initialAdminPassword. You know the drill. But — don’t skip the plugins. You’ll want: Pipeline Git Blue Ocean (optional, but nicer UI) Amazon ECR (if using Docker later) Oh, and this one burned me once — the jenkins user needs write access to /tmp. And to .ssh. And to the internet. Sounds obvious, right? So I once spent two hours debugging why pip install was failing. Turns out Jenkins couldn’t write to /tmp. (Yes. Really.) Permission denied. On /tmp. I still laugh about it. (Now.) Jenkins needs to clone your repo. Best way? SSH keys. Add the Jenkins user’s public key to your GitHub repo as a deploy key. Test it: sudo su - jenkins ssh -T [email protected] If this fails — and believe me, it will if you miss one step — the entire pipeline dies. Quietly. Like a ninja. Or a passive-aggressive coworker. And when Jenkins can’t pull code? You’re stuck. Team’s waiting. Standup’s tomorrow. Yeah. Don’t be that guy. Every build should start fresh. Always. Use a virtual environment. Every time. python3 -m venv venv source venv/bin/activate pip install -r requirements.txt Shared server. Shared Python path. One rogue pip install -user — and boom. Dependency hell. Silent test failures. Mysterious import errors. I saw a junior dev spend a day debugging a ModuleNotFoundError once — turned out another team’s pipeline installed an old version of requests globally. Nightmare. So: isolate. Always. Heart of Automation This is where the rubber meets the road. You’ll define your jenkins pipeline for django in a file called Jenkinsfile — right in your project root. Groovy syntax. Not Python. Not my favorite. But it works. Here’s a battle-tested starter: // Jenkinsfile pipeline { agent any environment { SECRET_KEY = credentials('django-secret-key') DEBUG = 'False' DB_HOST = 'prod-db.example.com' } stages { stage('Checkout') { steps { git branch: 'main', url: 'https://github.com/yourname/your-django-app.git' } } stage('Setup') { steps { sh ''' python3 -m venv venv source venv/bin/activate pip install -r requirements.txt ''' } } stage('Test') { steps { sh ''' source venv/bin/activate python manage.py test --settings=myapp.settings.test ''' } post { always { junit '**/test-reports/*.xml' } } } stage('Deploy to AWS') { steps { sh ''' source venv/bin/activate python manage.py collectstatic --noinput --settings=myapp.settings.prod sudo systemctl restart gunicorn ''' } } } post { success { echo "🚀 Deployed to production!" } failure { echo "❌ Pipeline failed. Check logs." } } } Quick breakdown: environment block uses Jenkins Credentials — no hardcoded secrets. Ever. Test stage runs Django tests and reports back via JUnit. You can see test history in Blue Ocean. Super useful. Deploy runs collectstatic and restarts Gunicorn. (Assuming you’re not using Docker.) And yes — this assumes you’re running Gunicorn behind Nginx. It’s old-school. It’s reliable. It’s what I still use for 80% of my projects. A junior I was mentoring asked me last week: “Why not just use GitHub Actions?” Fair question. My answer? If you’re deep in AWS, with private VPCs, internal DBs, on-prem-ish needs — Jenkins runs on your infra. It can SSH into boxes. It can talk to internal services. GitHub Actions? Not always. So flexibility matters. Also — we’ve used jenkins pipeline for django like, five times now. Because it’s not just a phrase. It’s a pattern. A workflow. A peace-of-mind machine. Loop Where are you deploying? Let’s be real — this pipeline is useless if it doesn’t push code somewhere. Your options: EC2 with Gunicorn + Nginx (simple, full control) Elastic Beanstalk (managed, easy scaling) ECS/Fargate (Docker-based, modern) For most teams I’ve worked with — especially early-stage — I'd recommend EC2. Cheaper. Easier to debug. You see the server. You can SSH. You can tail -f logs/gunicorn.log. Feels real. But if you’re on Elastic Beanstalk, no shame. Just update your deploy step: aws elasticbeanstalk create-deployment --application-name my-django-app --environment-name myapp-prod --version-label $BUILD_NUMBER Oh — and Jenkins needs AWS credentials. Best way? Assign an IAM role to the EC2 instance. Least privilege. beanstalk:CreateDeployment, s3:PutObject — that’s it. Worst way? Hardcoded keys in the Jenkinsfile. Please don’t. (I’ve seen it. In production. facepalm) Pro tip: Run migrate — but after the new code is in place. I once ran it before syncing code. Django tried to apply a migration that referenced a field not yet in the code. Database locked. Application down. Chaos. Lesson: ordering matters. Script it. Test it. Now — the fun part. Go to Jenkins dashboard. Create a new Pipeline job. Point it to your GitHub repo. Then — enable webhooks. So every git push triggers the pipeline. No manual clicks. No “who forgot to run CI?” emails. Test it: git commit --allow-empty -m "Trigger Jenkins" git push origin main If it runs — golden. If not? Check: Webhook delivery in GitHub Settings (look for 200s) Firewall rules (is port 8080 open?) SSH/Git permissions (again — always the culprit) And when it finally works? Magic. Not real magic. Just engineering. But it feels like magic. Your code. Your pipeline. Your server. All in sync. Like a well-tuned chord. Setting up a jenkins pipeline for django isn’t about tools. It’s about trust. Trust that your tests catch bugs. Trust that your deploys don’t break everything. Trust that a junior dev won’t accidentally nuke production. I used to be proud of how fast I could SSH into a server and fix things. Now? I’m proud when I don’t have to. That’s the win. Because now, I’m not a firefighter. I’m a builder. And honestly — the first time your pipeline blocks a bad deploy because a test failed? You’ll grin like an idiot. You’ve leveled up. Not because of Jenkins. But because you stopped doing stupid work. And that — that’s automation worth having. Use Jenkins’ built-in Credentials Store. Never, ever hardcode API keys. Inject them via the environment block using credentials('id'). They’ll be masked in logs — and you won’t leak them in a terminal screenshot. (Like I did once. Don’t ask.) Absolutely. Build your image in the pipeline, push to ECR, then deploy. Replace collectstatic with docker build and docker push. Same flow. New tools. Works great — especially on Fargate. Jenkins runs on your infrastructure. Need to access a private VPC? Internal database? Jenkins can. GitHub Actions? Sometimes not. Also — full control over workers, caching, permissions. It’s clunkier, sure. But it’s yours.