Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 2186816

Browse files
add information about deployment
1 parent 47227d3 commit 2186816

File tree

1 file changed

+75
-31
lines changed

1 file changed

+75
-31
lines changed

docs/guides/python/blender-render.mdx

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Now that we have defined resources, we can import our API and add some routes to
7777
```python title:src/services/main.py
7878
from nitric.application import Nitric
7979
from nitric.context import HttpContext
80-
from resources import rendered_bucket, main_api, blend_bucket, renderer_job
80+
from src.resources import rendered_bucket, main_api, blend_bucket, renderer_job
8181

8282
readable_rendered_bucket = rendered_bucket.allow("read")
8383
writeable_blend_bucket = blend_bucket.allow("write")
@@ -92,7 +92,7 @@ We'll then write a route for getting an image from the rendered bucket and a rou
9292
# !collapse(1:7) collapsed
9393
from nitric.application import Nitric
9494
from nitric.context import HttpContext
95-
from resources import rendered_bucket, main_api, blend_bucket, renderer_job
95+
from src.resources import rendered_bucket, main_api, blend_bucket, renderer_job
9696

9797
readable_rendered_bucket = rendered_bucket.allow("read")
9898
writeable_blend_bucket = blend_bucket.allow("write")
@@ -113,7 +113,7 @@ async def get_image(ctx: HttpContext):
113113
async def get_video(ctx: HttpContext):
114114
video = ctx.req.params["video"]
115115

116-
download_url = await readable_rendered_bucket.file(f"{video}.mpg")
116+
download_url = await readable_rendered_bucket.file(f"{video}.mkv")
117117

118118
ctx.res.headers["Location"] = download_url
119119
ctx.res.status = 303
@@ -129,7 +129,7 @@ The final route will be for sending off a `.blend` file for rendering. This will
129129
# !collapse(1:29) collapsed
130130
from nitric.application import Nitric
131131
from nitric.context import HttpContext
132-
from resources import rendered_bucket, main_api, blend_bucket, renderer_job
132+
from src.resources import rendered_bucket, main_api, blend_bucket, renderer_job
133133

134134
readable_rendered_bucket = rendered_bucket.allow("read")
135135
writeable_blend_bucket = blend_bucket.allow("write")
@@ -150,7 +150,7 @@ async def get_image(ctx: HttpContext):
150150
async def get_video(ctx: HttpContext):
151151
video = ctx.req.params["video"]
152152

153-
download_url = await readable_rendered_bucket.file(f"{video}.mpg").download_url(3600)
153+
download_url = await readable_rendered_bucket.file(f"{video}.mkv").download_url(3600)
154154

155155
ctx.res.headers["Location"] = download_url
156156
ctx.res.status = 303
@@ -184,7 +184,7 @@ Start by adding our imports and the resources we defined earlier.
184184
import os.path
185185
from nitric.context import JobContext
186186
from nitric.application import Nitric
187-
from resources import rendered_bucket, renderer_job, blend_bucket
187+
from src.resources import rendered_bucket, renderer_job, blend_bucket
188188

189189
readable_blend_bucket = blend_bucket.allow("read")
190190
writeable_rendered_bucket = rendered_bucket.allow("write")
@@ -201,7 +201,7 @@ Next, we'll add functionality to render the scene based on the file that is sent
201201
import os.path
202202
from nitric.context import JobContext
203203
from nitric.application import Nitric
204-
from resources import rendered_bucket, renderer_job, blend_bucket
204+
from src.resources import rendered_bucket, renderer_job, blend_bucket
205205

206206
readable_blend_bucket = blend_bucket.allow("read")
207207
writeable_rendered_bucket = rendered_bucket.allow("write")
@@ -233,7 +233,7 @@ Next, we'll read the blend file from the bucket that matches the key sent in the
233233
import os.path
234234
from nitric.context import JobContext
235235
from nitric.application import Nitric
236-
from resources import rendered_bucket, renderer_job, blend_bucket
236+
from src.resources import rendered_bucket, renderer_job, blend_bucket
237237

238238
readable_blend_bucket = blend_bucket.allow("read")
239239
writeable_rendered_bucket = rendered_bucket.allow("write")
@@ -255,7 +255,7 @@ async def render_image(ctx: JobContext):
255255
return ctx
256256

257257
# load the file from a bucket to a local file
258-
blend_file = await blend_bucket.file(blend_key).read()
258+
blend_file = await readable_blend_bucket.file(blend_key).read()
259259

260260
with open("input", "wb") as f:
261261
f.write(blend_file)
@@ -275,7 +275,7 @@ We'll then set the render engine to use CYCLES and use our GPU with CUDA. If you
275275
import os.path
276276
from nitric.context import JobContext
277277
from nitric.application import Nitric
278-
from resources import rendered_bucket, renderer_job, blend_bucket
278+
from src.resources import rendered_bucket, renderer_job, blend_bucket
279279

280280
readable_blend_bucket = blend_bucket.allow("read")
281281
writeable_rendered_bucket = rendered_bucket.allow("write")
@@ -297,7 +297,7 @@ async def render_image(ctx: JobContext):
297297
return ctx
298298

299299
# load the file from a bucket to a local file
300-
blend_file = await blend_bucket.file(blend_key).read()
300+
blend_file = await readable_blend_bucket.file(blend_key).read()
301301

302302
with open("input", "wb") as f:
303303
f.write(blend_file)
@@ -324,7 +324,7 @@ The next step is to set the output settings and render depending on whether the
324324
import os.path
325325
from nitric.context import JobContext
326326
from nitric.application import Nitric
327-
from resources import rendered_bucket, renderer_job, blend_bucket
327+
from src.resources import rendered_bucket, renderer_job, blend_bucket
328328

329329
readable_blend_bucket = blend_bucket.allow("read")
330330
writeable_rendered_bucket = rendered_bucket.allow("write")
@@ -346,7 +346,7 @@ async def render_image(ctx: JobContext):
346346
return ctx
347347

348348
# load the file from a bucket to a local file
349-
blend_file = await blend_bucket.file(blend_key).read()
349+
blend_file = await readable_blend_bucket.file(blend_key).read()
350350

351351
with open("input", "wb") as f:
352352
f.write(blend_file)
@@ -355,8 +355,8 @@ async def render_image(ctx: JobContext):
355355

356356
# Set render options
357357
bpy.context.scene.render.engine = 'CYCLES'
358-
# bpy.context.scene.cycles.preferences.compute_device_type = "CUDA"
359-
# bpy.context.scene.cycles.device = "GPU"
358+
bpy.context.scene.cycles.preferences.compute_device_type = "CUDA"
359+
bpy.context.scene.cycles.device = "GPU"
360360

361361
bpy.context.scene.render.filepath = "output"
362362

@@ -384,7 +384,7 @@ With the rendering complete, we'll read the contents of the outputted render and
384384
import os.path
385385
from nitric.context import JobContext
386386
from nitric.application import Nitric
387-
from resources import rendered_bucket, renderer_job, blend_bucket
387+
from src.resources import rendered_bucket, renderer_job, blend_bucket
388388

389389
readable_blend_bucket = blend_bucket.allow("read")
390390
writeable_rendered_bucket = rendered_bucket.allow("write")
@@ -406,7 +406,7 @@ async def render_image(ctx: JobContext):
406406
return ctx
407407

408408
# load the file from a bucket to a local file
409-
blend_file = await blend_bucket.file(blend_key).read()
409+
blend_file = await readable_blend_bucket.file(blend_key).read()
410410

411411
with open("input", "wb") as f:
412412
f.write(blend_file)
@@ -415,8 +415,8 @@ async def render_image(ctx: JobContext):
415415

416416
# Set render options
417417
bpy.context.scene.render.engine = 'CYCLES'
418-
# bpy.context.scene.cycles.preferences.compute_device_type = "CUDA"
419-
# bpy.context.scene.cycles.device = "GPU"
418+
bpy.context.scene.cycles.preferences.compute_device_type = "CUDA"
419+
bpy.context.scene.cycles.device = "GPU"
420420

421421
bpy.context.scene.render.filepath = "output"
422422

@@ -431,21 +431,20 @@ async def render_image(ctx: JobContext):
431431
bpy.context.scene.render.image_settings.file_format = 'PNG'
432432
bpy.ops.render.render(write_still=True)
433433

434-
with open("output", "rb") as f:
435-
image_bytes = f.read()
434+
file_extension = "mkv" if data_format == "video" else "png"
436435

437-
# swap the `.blend` extension for a `.png`
438-
file_extension = data_format == "video" if "mpg" else "png"
436+
with open(f"output.{file_extension}", "rb") as f:
437+
image_bytes = f.read()
439438

440-
await rendered_bucket.file(f"{blend_key}.{file_extension}").write(image_bytes)
439+
await writeable_rendered_bucket.file(f"{blend_key}.{file_extension}").write(image_bytes)
441440

442441
return ctx
443442

444443

445444
Nitric.run()
446445
```
447446

448-
## Blender Dockerfile
447+
## Creating GPU enabled dockerfiles
449448

450449
With our code complete, we can write a dockerfile that our batch job will run in. Start with the base image that copies our application code and resolves the dependencies using `uv`.
451450

@@ -686,17 +685,17 @@ nitric.yaml
686685
README.md
687686
```
688687

689-
We can update the `nitric.yaml` file to use the correct dockerfiles when running and deploying our applications.
688+
We can update the `nitric.yaml` file to allow our services and batch jobs to use the custom docker runtime we set up and point to the services directly.
690689

691690
```yaml title:nitric.yaml
692691
name: blender-render
693692
services:
694-
- match: src/services/*.py
693+
- match: src/services/main.py
695694
start: uv run watchmedo auto-restart -p *.py --no-restart-on-command-exit -R python -- -u $SERVICE_PATH
696695
runtime: python
697696

698697
batch-services:
699-
- match: src/jobs/*.py
698+
- match: src/jobs/renderer.py
700699
start: uv run watchmedo auto-restart -p *.py --no-restart-on-command-exit -R python -- -u $SERVICE_PATH
701700
runtime: blender
702701

@@ -732,10 +731,55 @@ preview:
732731
- batch-services
733732
```
734733

735-
## Run Locally
734+
## Run your renderer locally
736735

737-
We can test the application locally using the following code:
736+
We can test our application locally using:
738737

739-
```
738+
```bash
740739
nitric run
741740
```
741+
742+
You can then use any HTTP client capable of posting binary in the request, like the Nitric [local dashboard](/get-started/foundations/projects/local-development#local-dashboard).
743+
744+
```bash
745+
curl --request POST --data-binary "@cube.blend" http://localhost:4001/cube
746+
```
747+
748+
## Deploy to the cloud
749+
750+
At this point, you can deploy what you've built to any of the supported cloud providers. In this example we'll deploy to AWS. Start by setting up your credentials and configuration for the [nitric/aws provider](/providers/pulumi/aws).
751+
752+
Next, we'll need to create a stack file (deployment target). A stack is a deployed instance of an application. You might want separate stacks for each environment, such as stacks for `dev`, `test`, and `prod`. For now, let's start by creating a file for the `dev` stack.
753+
754+
The `stack new` command below will create a stack named `dev` that uses the `aws` provider.
755+
756+
```
757+
nitric stack new dev aws
758+
```
759+
760+
Edit the stack file `nitric.dev.yaml` and set your preferred AWS region, for example `us-east-1`.
761+
762+
<Note>
763+
You are responsible for staying within the limits of the free tier or any
764+
costs associated with deployment.
765+
</Note>
766+
767+
Let's try deploying the stack with the `up` command:
768+
769+
```bash
770+
nitric up
771+
```
772+
773+
When the deployment is complete, go to the relevant cloud console and you'll be able to see and interact with your Blender rendering application.
774+
775+
To tear down your application from the cloud, use the `down` command:
776+
777+
```bash
778+
nitric down
779+
```
780+
781+
## Summary
782+
783+
In this guide, we've created a remote Blender Renderer using Python and Nitric. We showed how to use batch jobs to run long-running workloads and connect these jobs to buckets to store rendered output. We also demonstrated how to expose buckets using simple CRUD routes on a cloud API. Finally, we were able to create dockerfiles with GPU support for optimal Blender rendering speeds.
784+
785+
For more information and advanced usage, refer to the [Nitric documentation](/docs).

0 commit comments

Comments
 (0)