configuring bear blog's media proxy
> Update: Test instance is now running on deadauthor.org (only my own domains [whitelisted](https://static.mgx.me/images/2025/1761036059614_bearblog-test-instance-sign-up.webp) for signups and testing). Here's the Dockerfile.
I've been a [Bear user for a while](https://mgx.me/by-the-power-of-bear-blog), and today I finally tried setting up Bear locally. Since I already have my [Cloudflare Tunnel](https://mgx.me/using-a-realme-laptop-for-containerized-apps-via-cloudflare-tunnel) configured, I figured -- why not containerize Bear and experiment a bit? This blog post walks through how I configured Bear's media proxy to serve images from DigitalOcean Spaces with clean, branded URLs.
## The Setup
I connected my own DigitalOcean Spaces bucket to Bear Blog and configured it to serve media through branded proxy URLs instead of direct object storage links. This approach allows media to be served through blog's subdomain while maintaining a single backend storage bucket.
**The goal:**
- Serve media through clean, branded URLs: `https://notes.deadauthor.org/media/notes/chonker.jpg`
- Instead of direct storage URLs: `https://bearblog.sfo3.digitaloceanspaces.com/notes/chonker.jpg`
## Configuration Steps
#### 1. URL Pattern Configuration (`blogs/urls.py`)
Define the media proxy route to accept file paths with multiple segments:
```python
path('media/', media.image_proxy_no_slash, name="image-proxy"),
```
Using `` is crucial here -- it allows the URL pattern to capture paths containing slashes (like `mgx/chonker.jpg`), whereas `` would only capture the first segment.
#### 2. Media Proxy Function (`blogs/views/media.py`)
Implement the proxy handler to fetch media from DigitalOcean Spaces and stream it back to the client:
```python
def image_proxy_no_slash(request, img):
""Handle media proxy URLs without trailing slash""
return image_proxy(request, img)
def image_proxy(request, img):
# Remove any trailing slashes from the path
if img.endswith('/'):
img = img.rstrip('/')
# Construct the DigitalOcean Spaces URL
remote_url = f'https://{bucket_name}.sfo3.digitaloceanspaces.com/{img}'
# Stream the content from the remote URL
response = requests.get(remote_url, stream=True, timeout=10)
# Define a generator to yield chunks of the response content
def generate():
for chunk in response.iter_content(chunk_size=8192):
yield chunk
# Return a StreamingHttpResponse
return StreamingHttpResponse(
generate(),
status=response.status_code,
content_type=response.headers['Content-Type']
)
```
#### 3. Enhanced Copy URL Functionality (`blogs/models.py` & `templates/dashboard/media.html`)
Add a property to Media model that generates clean proxy URLs:
```python
@property
def proxy_url(self):
""Generate proxy URL for this media file""
# Extract the file path from the full URL
# URL format: https://bearblog.sfo3.digitaloceanspaces.com/notes/filename.webp
# I want: https://notes.deadauthor.org/media/notes/chonker.jpg
file_path = self.url.split('/')[-2] + '/' + self.url.split('/')[-1]
return f"https://{self.blog.subdomain}.deadauthor.org/media/{file_path}"
```
Update the copy URL button in media dashboard to use the proxy URL:
```html
```