{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"where-automation-runs","title":"Where it runs when your laptop is shut","url":"https://addaly.com/learn/agents-and-automation/where-automation-runs","position":58,"of":72,"minutes":8,"answer":"Pick the runner by run duration, state and who else can restart it, and remember cron gets almost no environment, so a variable exported in your shell profile is not there.","keyIdea":"Pick the runner by run duration, state and who else can restart it, and remember cron gets almost no environment, so a variable exported in your shell profile is not there."},"body":{"format":"markdown","text":"## The options, with real numbers\n\n**A small server with a timer.** A basic instance from Hetzner, DigitalOcean, Contabo or similar runs at roughly four to six dollars a month. Add a systemd timer:\n\n```ini\n[Timer]\nOnCalendar=Mon *-*-* 06:00:00\nPersistent=true\n```\n\n`journalctl -u yourjob` gives you logs with no additional tooling. This is the simplest thing that is yours, and it is a perfectly respectable answer for a long time.\n\n**GitHub Actions on a schedule.** Free minutes on public repositories, five-minute minimum granularity, no server to maintain. Three caveats worth knowing before you rely on it: runs at popular times are delayed by minutes, scheduled workflows are disabled after roughly 60 days without repository activity, and a job needing secrets and network access is a strange place to put business logic. Fine for a daily report; wrong for anything time-critical.\n\n**Serverless with a timer**: Cloudflare Workers cron triggers, a Lambda behind a scheduled event, a platform's cron feature. Very cheap, nothing to patch. Check the limits carefully: platforms cap CPU time, wall-clock time or both, and an agent that waits four minutes on HTTP calls may be fine on one platform and impossible on another. Read the specific numbers before you design for it.\n\n**A container on a managed platform** (Fly, Render, Railway, Cloud Run) when the work is long-running or holds a queue worker open.\n\n**The visual tool's own cloud.** You pay so that you run nothing. Entirely reasonable if the automation is small and the alternative is a server nobody wants to own.\n\n## The three questions that decide it\n\n1. **How long does one run take?** Past about fifteen minutes, most serverless options are out.\n2. **Does it hold state, or need a queue worker running continuously?** If yes, you want a container or a server.\n3. **Who else can restart it?** A cron job on your laptop is a single point of failure with a name, and the name is yours.\n\n## The environment trap\n\nThis one costs teams weeks, and the mechanism is unglamorous.\n\n**Cron runs with almost no environment.** No profile is sourced. `PATH` is typically just `/usr/bin:/bin`. Variables you exported in `.bashrc`, `.zshrc` or `.zprofile` are absent, so a script that works perfectly when you run it by hand fails under cron with a message about a missing key or a command not found.\n\nThe same applies to CI runners, to systemd services and to stdio subprocesses. All of them get the environment their parent gives them, and none of them get yours.\n\nThe fixes are boring and reliable: absolute paths to every binary, an explicit `EnvironmentFile=` for systemd, secrets loaded from a file or a secret store rather than assumed, and a first line in the script that fails loudly if a required variable is missing. That last one turns a silent Tuesday-morning mystery into an error message.\n\n## Time zones\n\nCron runs in the server's timezone, which is usually UTC and occasionally something a previous engineer chose. Set it explicitly (`CRON_TZ=` in the crontab, or the timer's own setting) and be aware that a job scheduled at 02:30 local time does not run on the day the clocks move forward. Schedule anything critical in UTC.\n\n## Secrets\n\nNot in the repository, not in the code, not in the crontab line. Environment file with restrictive permissions at minimum, a secret store at best, and rotate them on a schedule you keep."},"check":{"question":"A Python script that calls a paid API runs correctly from the developer's terminal. Scheduled with cron, it fails every night with a missing API key error. The key is exported in the developer's `.zprofile`. What is happening?","options":[{"text":"Cron does not source shell profiles, so the exported variable does not exist in the job's environment; it needs an explicit environment file or a secret store.","correct":true},{"text":"The crontab entry has the wrong schedule and is running as a different user with a different profile.","correct":false,"tempting":"Names a real class of cron bug, but a wrong schedule produces no run at all, and the failure here is a specific missing variable."},{"text":"The API key expired and needs rotating.","correct":false,"tempting":"Would fail in the terminal too, and the terminal run succeeds, which is the fact that identifies this as an environment problem."},{"text":"Cron cannot make outbound network calls without additional configuration.","correct":false,"tempting":"Invents a restriction that does not exist; cron jobs have the machine's ordinary network access."}]},"prev":{"slug":"triggers-and-schedules","title":"How the thing starts","url":"https://addaly.com/learn/agents-and-automation/triggers-and-schedules"},"next":{"slug":"queues-and-concurrency","title":"Rate limits, workers and the thundering herd","url":"https://addaly.com/learn/agents-and-automation/queues-and-concurrency"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Where it runs when your laptop is shut', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/where-automation-runs (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Where it runs when your laptop is shut","courseTitle":"Agents and Automation","module":{"index":7,"slug":"shipping-and-operating","title":"Shipping it, and keeping it alive"},"answer":null,"keyIdea":"Pick the runner by run duration, state and who else can restart it, and remember cron gets almost no environment, so a variable exported in your shell profile is not there.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-options-with-real-numbers","runs":[{"text":"The options, with real numbers"}]},{"t":"p","runs":[{"text":"A small server with a timer.","b":true},{"text":" A basic instance from Hetzner, DigitalOcean, Contabo or similar runs at roughly four to six dollars a month. Add a systemd timer:"}]},{"t":"code","lang":"ini","label":"INI","text":"[Timer]\nOnCalendar=Mon *-*-* 06:00:00\nPersistent=true"},{"t":"p","runs":[{"text":"journalctl -u yourjob","code":true},{"text":" gives you logs with no additional tooling. This is the simplest thing that is yours, and it is a perfectly respectable answer for a long time."}]},{"t":"p","runs":[{"text":"GitHub Actions on a schedule.","b":true},{"text":" Free minutes on public repositories, five-minute minimum granularity, no server to maintain. Three caveats worth knowing before you rely on it: runs at popular times are delayed by minutes, scheduled workflows are disabled after roughly 60 days without repository activity, and a job needing secrets and network access is a strange place to put business logic. Fine for a daily report; wrong for anything time-critical."}]},{"t":"p","runs":[{"text":"Serverless with a timer","b":true},{"text":": Cloudflare Workers cron triggers, a Lambda behind a scheduled event, a platform’s cron feature. Very cheap, nothing to patch. Check the limits carefully: platforms cap CPU time, wall-clock time or both, and an agent that waits four minutes on HTTP calls may be fine on one platform and impossible on another. Read the specific numbers before you design for it."}]},{"t":"p","runs":[{"text":"A container on a managed platform","b":true},{"text":" (Fly, Render, Railway, Cloud Run) when the work is long-running or holds a queue worker open."}]},{"t":"p","runs":[{"text":"The visual tool’s own cloud.","b":true},{"text":" You pay so that you run nothing. Entirely reasonable if the automation is small and the alternative is a server nobody wants to own."}]},{"t":"h2","id":"the-three-questions-that-decide-it","runs":[{"text":"The three questions that decide it"}]},{"t":"ol","start":1,"items":[[{"text":"How long does one run take?","b":true},{"text":" Past about fifteen minutes, most serverless options are out."}],[{"text":"Does it hold state, or need a queue worker running continuously?","b":true},{"text":" If yes, you want a container or a server."}],[{"text":"Who else can restart it?","b":true},{"text":" A cron job on your laptop is a single point of failure with a name, and the name is yours."}]]},{"t":"h2","id":"the-environment-trap","runs":[{"text":"The environment trap"}]},{"t":"p","runs":[{"text":"This one costs teams weeks, and the mechanism is unglamorous."}]},{"t":"p","runs":[{"text":"Cron runs with almost no environment.","b":true},{"text":" No profile is sourced. "},{"text":"PATH","code":true},{"text":" is typically just "},{"text":"/usr/bin:/bin","code":true},{"text":". Variables you exported in "},{"text":".bashrc","code":true},{"text":", "},{"text":".zshrc","code":true},{"text":" or "},{"text":".zprofile","code":true},{"text":" are absent, so a script that works perfectly when you run it by hand fails under cron with a message about a missing key or a command not found."}]},{"t":"p","runs":[{"text":"The same applies to CI runners, to systemd services and to stdio subprocesses. All of them get the environment their parent gives them, and none of them get yours."}]},{"t":"p","runs":[{"text":"The fixes are boring and reliable: absolute paths to every binary, an explicit "},{"text":"EnvironmentFile=","code":true},{"text":" for systemd, secrets loaded from a file or a secret store rather than assumed, and a first line in the script that fails loudly if a required variable is missing. That last one turns a silent Tuesday-morning mystery into an error message."}]},{"t":"h2","id":"time-zones","runs":[{"text":"Time zones"}]},{"t":"p","runs":[{"text":"Cron runs in the server’s timezone, which is usually UTC and occasionally something a previous engineer chose. Set it explicitly ("},{"text":"CRON_TZ=","code":true},{"text":" in the crontab, or the timer’s own setting) and be aware that a job scheduled at 02:30 local time does not run on the day the clocks move forward. Schedule anything critical in UTC."}]},{"t":"h2","id":"secrets","runs":[{"text":"Secrets"}]},{"t":"p","runs":[{"text":"Not in the repository, not in the code, not in the crontab line. Environment file with restrictive permissions at minimum, a secret store at best, and rotate them on a schedule you keep."}]}],"check":{"question":[{"text":"A Python script that calls a paid API runs correctly from the developer’s terminal. Scheduled with cron, it fails every night with a missing API key error. The key is exported in the developer’s "},{"text":".zprofile","code":true},{"text":". What is happening?"}],"options":[{"text":[{"text":"Cron does not source shell profiles, so the exported variable does not exist in the job’s environment; it needs an explicit environment file or a secret store."}],"correct":true,"tempting":null},{"text":[{"text":"The crontab entry has the wrong schedule and is running as a different user with a different profile."}],"correct":false,"tempting":[{"text":"Names a real class of cron bug, but a wrong schedule produces no run at all, and the failure here is a specific missing variable."}]},{"text":[{"text":"The API key expired and needs rotating."}],"correct":false,"tempting":[{"text":"Would fail in the terminal too, and the terminal run succeeds, which is the fact that identifies this as an environment problem."}]},{"text":[{"text":"Cron cannot make outbound network calls without additional configuration."}],"correct":false,"tempting":[{"text":"Invents a restriction that does not exist; cron jobs have the machine’s ordinary network access."}]}],"order":[1,2,0,3],"why":null},"prev":{"slug":"triggers-and-schedules","title":"How the thing starts","minutes":8},"next":{"slug":"queues-and-concurrency","title":"Rate limits, workers and the thundering herd","minutes":9}}}