Understanding Copado Functions for Efficient Deployment
There are already two articles on this site about Copado Functions. The first covers what a Function is and how to build one, and the second covers the less obvious business of making it run from a Flow or from Apex. Neither answers the question that turns up about three months into production, which is the one I want to take on here.
Functions are easy to build. They are also easy to waste. Once a team discovers them, the pipeline quietly fills with containers that do forty seconds of useful work and consume the same allocation as the one doing something that matters. Efficient deployment is not about cleverer scripts. It is about being deliberate regarding what runs, how often, and in what.
The execution is the unit of cost
A Function does not run inside Salesforce. It runs in a container on the Copado platform, instantiated for the job and discarded when the job completes. That architecture is the whole reason Functions are useful, because it gives you a real shell against a real credential. It is also the reason they are metered.
Every execution consumes credits from your allocation, and you will meet that fact at the least convenient moment, usually in a sandbox, usually mid-sprint:
Treat the allocation the way you would treat any other finite production resource. Know roughly what you spend per release, and know which functions are spending it.
The image is a decision, not a default
Every image inherits from the core image, so what differs is weight, not capability. Choose the lightest image that already contains what you need. If the work is curl, jq and a bit of git, the core image is the right answer. If it genuinely needs the Salesforce CLI, take the metadata image rather than installing the CLI at runtime, because an install step in a script is not a one-off cost. It is paid again on every execution, forever, and it is the most common reason a Function that should take thirty seconds takes four minutes.
Worker size is where optimism gets expensive
Start small and stay small. A larger worker is the right call when you have measured a function that is genuinely compute or memory bound. It is the wrong call as a reflex, which is most of the time. Most slow Functions are slow because they are waiting on an API, retrieving more metadata than they need, or installing tooling, and none of those get faster on a bigger box.
The same scepticism applies to the timeout. Leaving it blank so the function runs to conclusion is fine for work you trust. For anything calling an external system, a timeout is cheaper than a container sitting there until something else gives up.
Run once over many, not many times over one
This is the biggest efficiency lever and it is almost always available. A Function invoked once per record is a container per record. A Function invoked once, which queries the set and iterates inside the shell, is one container. Checking a value across every environment in the pipeline is one execution, not one per environment, and the difference compounds every time it is scheduled.
The counter-intuitive half is not consolidating everything into one enormous Function. Keep the unit of work small enough that a failure is cheap to retry, then chain them. Standardising parameters across every function makes chaining nearly free, which is the argument made at length in the second article and is worth the discipline for this reason alone.
Make the result record do the work
Every execution creates a result record and you can attach files to it. Use that. Write what the function found, what it changed and what it skipped, and attach the output. The alternative is re-running the function to discover what the last run did, which costs another execution to answer a question you should already have in writing. On any pipeline with an audit obligation, that record is also the cheapest evidence you will ever collect.
And know when not to use one
If the work is entirely inside a single Salesforce org and can be expressed in Apex or Flow, do it in Apex or Flow. A Function earns its keep when you need the command line, a tool that does not exist on platform, or a credential against a different org. When it is just moving records around in the org you are already in, the container is overhead with extra steps.