Baking smoke is very similar to baking fluid, so I will quote brecht's answer:
It's a bit more complicated than it could be because fluid simulating baking is only exposed as an operator. That means it expects an active object in the context, which is usually provided by the user interface that is not available in background mode.
However, it is possible to override context manually, see this answer as well as the documentation.
For a smoke simulation, this might look like:
for scene in bpy.data.scenes:
for obj in scene.objects:
for modifier in obj.modifiers:
if modifier.type == 'FLUID' and modifier.fluid_type == 'DOMAIN':
if modifier.domain_settings.domain_type == 'GAS':
with bpy.context.temp_override(scene=scene, active_object=obj):
# bpy.ops.fluid.free_all() # if you'd like to free existing bakes first
updated for blender 3.4+. for pre-mantaflow version (<2.82) see this revision
This will bake the selected cache for all domain objects in the file.
You can then run this python script in blender with
blender --background /path/to/file.blend --python /path/to/script.py
Or the short way:
blender -b /path/to/file.blend -P /path/to/script.py