R
Railway5mo ago
darth

Recommended approach for django-crontab on railway

I'm using the django-crontab package to schedule a daily job that works fine when run locally, but I haven't been able to figure out how to get it working on Railway. I've tried updating my Procfile as shown below, but haven't had any luck. What's the recommended approach to scheduling a job on Railway to interact with a Django app? web: python manage.py crontab add && python manage.py migrate && python manage.py collectstatic --noinput --clear && gunicorn django_project.wsgi Project ID for reference: 3eeb1796-c531-4b55-9016-db32a2502d61
75 Replies
Percy
Percy5mo ago
Project ID: 3eeb1796-c531-4b55-9016-db32a2502d61
Brody
Brody5mo ago
so python manage.py crontab add is the command you run to start the cron tasks?
darth
darth5mo ago
Correct. Once added, it then looks inside the Django settings to find the cron schedule and task to run.
Brody
Brody5mo ago
and is that a one off thing, or does the command stay alive and run on the set schedule
darth
darth5mo ago
It runs according to the cron schedule defined in the Django settings.
Brody
Brody5mo ago
yes i did send before i was done typing 😅 so you will want two railway services, one service will run just the crontab, and the other will run just the django site. the identical services will be deploying into the same project, they will have the same repo, same branch, and same service variables, the only difference would be the service names, and the start commands. name one service something like Crontab and the other mySite <-- choose a better name once you have two identical services, in the Crontab service's settings, change the start command to python manage.py crontab add, in the mySite service's settings, change the start command to python manage.py migrate && python manage.py collectstatic --noinput --clear && gunicorn django_project.wsgi
darth
darth5mo ago
No worries... python manage.py crontab add adds the active job to run, and then runs as part of the Django app itself. Example docs here -> https://pypi.org/project/django-crontab/ Because it's part of the app, I wasn't sure if things would still work on Railway if I were to split them out separately...
PyPI
django-crontab
dead simple crontab powered job scheduling for django
Brody
Brody5mo ago
okay then hold on, i might have misjudged something it looks like python manage.py crontab add adds the jobs to the systems cron scheduler but deployments dont come with a cron scheduler, do you get any errors in the deploy logs?
darth
darth5mo ago
No errors in the deploy logs.
Brody
Brody5mo ago
do you think you could put together the most basic django app with a cron job that prints a message every minute? with that i could dig into this issue
darth
darth5mo ago
Yea, I can spin up a very basic version that does that. How do the logs need to be emitted to show up in Railway? Does a simple python 'print' statement suffice?
Brody
Brody5mo ago
yes print should do just fine, thanks!!
darth
darth5mo ago
Should I just share the github repo to you for the basic app, or actually deploy it on railway as well?
Brody
Brody5mo ago
share it with me, just make sure it works locally, ill do my best to get it running on railway, then whatever i do, we apply to your actual app so yeah django app that just has a hello world page and prints hello world every minute, no need for assets or anything like that
darth
darth5mo ago
This should be all set to deploy with no additional changes needed -> https://github.com/gymanji/railway-django-crontab
GitHub
GitHub - gymanji/railway-django-crontab
Contribute to gymanji/railway-django-crontab development by creating an account on GitHub.
Brody
Brody5mo ago
cool, what command did you run locally to have it start printing hello world every minute?
darth
darth5mo ago
Assuming you're running it locally, you'd run: First time: 1. pip3 install -r requirements.txt 2. python3 manage.py migrate After that: 1. python3 manage.py crontab add 2. python3 manage.py runserver
Brody
Brody5mo ago
thats for the database stuff, what about starting the cron jobs?
darth
darth5mo ago
hit enter too fast.. see updated commands above
Brody
Brody5mo ago
haha you did the same thing as me
darth
darth5mo ago
The 'print' statement from the cron.py file shows up in /var/mail/{user}
Brody
Brody5mo ago
what do you mean by that? shouldnt i see it in the railway deploy logs if its working correctly?
darth
darth5mo ago
Any time you change anything related to the cron.py file or the cron schedule (set at the end of "django_project/settings.py"), you'll need to re-add the crontab. I didn't test deploying the simple app on railway, I'm just sharing what I saw in the local behavior.
Brody
Brody5mo ago
gotcha will update if i found a solution or if i have any questions! so i was correct when i said you would need two services. delete your Procfile add this nixpacks.toml file to your project:
[phases.setup]
aptPkgs = ['...', 'cron']
[phases.setup]
aptPkgs = ['...', 'cron']
deploy two nearly identical services, same variables, same repo, etc (the cron service doesnt need a domain) on the service that is going to do the cron tasks, set this as the start command python manage.py crontab add && cron -f on the service that will be django, set this as the start command python manage.py migrate && python manage.py collectstatic --noinput --clear && gunicorn django_project.wsgi do ask if you need extra clarification on anything
darth
darth5mo ago
I followed the above steps, but am seeing the following error in the Deploy logs for the cron version of the service /usr/bin/crontab: not found.
Brody
Brody5mo ago
seems like you didn't add the nixpacks.toml file
darth
darth5mo ago
It's added to the root of the git repo. Does it need to be in a different directory?
Brody
Brody5mo ago
what's the root directory of the service set to
darth
darth5mo ago
Also the root of the git repo. Where did you place the nixpacks.toml file when you tested separately with the simple app?
Brody
Brody5mo ago
in the root of the project can you send the build logs of your latest deployment please also a screenshot of your railway project
darth
darth5mo ago
Hold on, I just made one minor change and it looks like that error is gone now. I'll get back in a bit to confirm whether or not it's working once the cron job has a chance to run.
No description
Brody
Brody5mo ago
may I still see the project screenshot please
darth
darth5mo ago
This?
Brody
Brody5mo ago
perfect, I assume the django app is working as intended?
darth
darth5mo ago
Yes, it seems to be.
Brody
Brody5mo ago
alright, let me know if that cron job runs, but from the logs I got during my testing, my logs looked exactly like yours and it worked for me, so very good sign
darth
darth5mo ago
Fingers crossed then! I appreciate the assistance as well!
Brody
Brody5mo ago
happy to help
darth
darth5mo ago
Ok, so I've tried a few different tweaks now and still don't see the cron process executing. There are multiple print statements inside the function called, but none of them show up in the logs area. The logs aside, the function is also configured to send an email once it completes that isn't arriving. The deploy logs show this right after running, which seems to look normal (but there's no output otherwise). Here are the most recent set of build logs as well: ``
Brody
Brody5mo ago
there isnt going to be logs, its a background process
darth
darth5mo ago
Ok, good to know. In the start command, does it need to have "... && cron -f" appended at the end?
Brody
Brody5mo ago
yes, use the start command as i wrote it
darth
darth5mo ago
Yes, it's using "python manage.py crontab add && cron -f" right now.
Brody
Brody5mo ago
email auth is likely failing then whos your email provider
darth
darth5mo ago
Gmail. The config works locally though, so the only difference would be in how the app accesses the variables inside Railway. However, the app has no problems accessing the database variables set in Railway 🤷‍♂️
Brody
Brody5mo ago
gmail is super well known to block auth from non residential ip addresses id recommend using your own domain with zoho instead, ive had 100% success rate with a zoho email on railway
darth
darth5mo ago
Is there anyway to view process info to confirm that it's an IP block issue first?
Brody
Brody5mo ago
since its a background task it cant log to the parent stdout/stderr, therefore you can't see it in the deploy logs
darth
darth5mo ago
Is there a 'tmp' local directory or something similar that a log message could be written to?
Brody
Brody5mo ago
you dont have access to the filesystem so theres no point
darth
darth5mo ago
Ok, worth a shot 😆
Brody
Brody5mo ago
maybe time to move to the python scheduler module? with that you have no need to install cron via nix, and you can see all prints in the deploy logs (two services still needed) your crontab module is from 2016 after all, time for an update
darth
darth5mo ago
Might be the best route.
Brody
Brody5mo ago
i strongly think so from your log, you only have one job, i cant imagine it would be too hard to switch over to the scheduler module
darth
darth5mo ago
No, likely not a major switch. Why would using the schedule module still require two services?
Brody
Brody5mo ago
the event loop is likely a blocking task so it needs it own service, plus its just generally better practice but im not too sure on what is the best schedular module, i think sched is built in? or this one https://schedule.readthedocs.io/en/stable/ it looks very user friendly, this is what i think i would choose
darth
darth4mo ago
Circling back on this in case it helps others, but ultimately I ended up doing the following: 1. Moved the function into a custom django admin command 2. Create a duplicated version of the django service in Railway 3. Set the "Cron Schedule" inside the service settings with a 'Custom Start Command' that executes the custom django admin command
holy shnikes
holy shnikes4mo ago
@darth thanks for the update, I'm trying to deploy a weekly cron email for my django app and have been thinking about these same problems. To clarify, step 2 is necesary because you don't want to take up resources on your primary django service?
Brody
Brody4mo ago
it's necessary because a single railway service can't be a cron job and a regular service at the same time, also it's good practice incase the email schedule crashes, it won't take down the django site
holy shnikes
holy shnikes4mo ago
Gotcha, thanks @Brody If I have multiple cron jobs (let's say, for different emails to users that run at different frequencies), it's normal/a best practice to spin up a new service for each one? Seems like it might be expensive but the pro plan billing page indicates "Unlimited concurrent builds", so... fine I guess? builds == services in this case?
Brody
Brody4mo ago
the pro plan does not have unlimited concurrent builds anymore, that needs to be removed. but yeah a service for the different frequencies seems good to me, after all, if you use railways cron scheduler properly you would only be charged for the resources used while the job is running, the rest of the time it would be off completely oh and no, builds does not equal services, builds are the time when you deploy your code and it's being built (it's now 10 concurrent builds)
holy shnikes
holy shnikes4mo ago
Ah awesome, thanks. the rest of the time it would be off completely - do I need to turn on App Sleeping for that?
Brody
Brody4mo ago
nope, if you use railways cron scheduler properly the process goes like so: schedule time reached -> railway starts your service up -> your service does what it does -> your service exits as soon as it's done. and if your service exits when it's done, then it's not using resources until the next scheduled time
holy shnikes
holy shnikes4mo ago
(I'm using Railway's cron scheduler)
Brody
Brody4mo ago
now I keep saying properly because everything I said is only correct if your service exits as soon as it's done doing what it needs to do
holy shnikes
holy shnikes4mo ago
Cool. I assume nothing happens after I run my custom django management command, but I guess I'll need to research django a little ``` "deploy": { "numReplicas": 1, "startCommand": "python manage.py migrate && python manage.py collectstatic --no-input && python manage.py new_custom_management_command && gunicorn -b [::]:$PORT faves_project.wsgi", "restartPolicyType": "ON_FAILURE", "restartPolicyMaxRetries": 10 }
Brody
Brody4mo ago
is that the command you have the cron service running?
holy shnikes
holy shnikes4mo ago
Yeah, I guess I could not run gunicorn after the custom command 🤔
Brody
Brody4mo ago
because that will start django in the cron service and you will be charged for resource usage 24/7 since gunicorn running django is a long running process you can set the appropriate start command in the service settings
holy shnikes
holy shnikes4mo ago
Hm so just removing gunicorn should do the trick I hope
Brody
Brody4mo ago
it's possible, but you'd need to check locally, you also don't need to do migrations or collect static
holy shnikes
holy shnikes4mo ago
My emails may rely on the static content or migrations so I'm leaving them in for now
Brody
Brody4mo ago
oh okay that makes sense but definitely check if your added custom command exits when it's done
holy shnikes
holy shnikes4mo ago
It's just a python function that loops through all users and possibly sends an email to each one, so I assume so
Brody
Brody4mo ago
you'd want to be sure