How I run and test a full-stack automation system with headless Chromium, message queues, and databases—entirely from a phone-based development workflow.
Working Within the Constraints
When people think of software engineering, they often picture dual 4K monitors, mechanical keyboards, and powerful workstations. But with cloud development environments and the right tooling, the workstation doesn't always have to be where the heavy work happens.
Today, I was contributing to Replex—an open-source video automation tool that uses Playwright, BullMQ, Redis, MongoDB, Express, and Next.js to autonomously browse websites and record demo reels across different viewports.
Here's a look at my development setup, how I optimize my cloud resources, and how I run the different services from a phone.
The Architecture at a Glance
[ My Phone / Local Environment ]
├── Local Git Repo (Editing & quick checks without burning Codespace quota)
└── Mobile Browser (http://localhost:3000)
│
▼ (gh cs ports forward 3000:3000 5000:5000)
[ GitHub Codespaces Cloud Container ]
├── 🐳 Docker (Redis:6379 & MongoDB:27017)
├── 🖥️ screen: Next.js Frontend (:3000)
├── 🖥️ screen: Express API Server (:5000)
├── 🖥️ screen: BullMQ Worker + Headless Chromium
└── ☁️ Cloudinary Video Pipeline
The Workflow: Step by Step
1. Conserving Cloud Quota (Local-First Editing)
GitHub Codespaces gives me a monthly pool of compute time. To make the most of it:
- I write code and perform initial lint and syntax checks locally on my device.
- Commit and push the branch to GitHub.
- SSH into my Codespaces environment and pull the latest changes.
# Pull the latest changes into the Codespace
git pull origin improve-mobile-recording
Sometimes I code directly inside Codespaces, but when I don't need the cloud environment yet, doing the lightweight work locally saves those minutes for when I actually need them.
2. Spinning Up Data Stores with Docker
For Redis and MongoDB, Docker saves me from installing and configuring each service directly in the Codespace.
Instead of setting up each database separately and managing additional services, I can spin both of them up in a couple of commands:
# 1. Start Redis in the background for BullMQ task queues
docker run -d --name redis-local -p 6379:6379 redis:alpine
# 2. Start MongoDB in the background for persistent video history
docker run -d --name mongo-local -p 27017:27017 mongo:latest
That's it. Both services are running and ready for the application to connect to them.
3. Setting Up Headless Automation
The worker needs a Chromium browser environment to automate and record web interactions, so I install the required dependencies:
cd backend && npm install
npx playwright install --with-deps chromium
cd ../frontend && npm install
4. Running Services with GNU screen
The frontend, API, and worker all need to run simultaneously. Rather than keeping multiple terminal sessions open, I use GNU screen to run them as detached sessions:
# Session 1: Next.js Frontend
screen -dmS frontend bash -c "cd frontend && npm run dev"
# Session 2: Express Backend Server
screen -dmS backend-server bash -c "cd backend && npm run dev"
# Session 3: BullMQ Playwright Worker
screen -dmS backend-worker bash -c "cd backend && npm run worker"
I can inspect the worker logs whenever I need to:
screen -r backend-worker
And detach again with Ctrl+A, then D.
This lets all the services continue running while I use another terminal session for the next part of the setup.
5. The Magic Link: Port Forwarding
At this point, the application is running inside Codespaces. I still need to interact with it from my phone's browser.
That's where GitHub CLI port forwarding comes in.
I initially forgot to forward the backend's port too, assuming the frontend could reach it directly in Codespaces. The API calls wouldn't resolve from my phone, so I added 5000:5000 to the forward command:
gh cs ports forward 3000:3000 5000:5000
Forwarding:
• Local port 3000 ──▶ Codespaces Next.js UI (:3000)
• Local port 5000 ──▶ Codespaces Express API (:5000)
Now I can open http://localhost:3000 in my phone's browser and interact with the application running in the Codespace.
The frontend can then dispatch background jobs to the API, which communicates with Redis and the worker running Playwright and Chromium.
6. Verification & End-to-End Testing
Once everything is running, I verify that the pieces are actually communicating.
# Test API connectivity through the tunnel
curl http://localhost:5000/api/history
# Output: [] (Healthy 200 response!)
Then I can trigger an actual recording job and inspect the worker's output from its detached screen session:
screen -r backend-worker
The worker then processes the job:
[Job 1] Started for URL: https://example.com on mobile
[Recorder] Warming up cache...
[Recorder] Found menu button via: button[aria-label*="menu" i]. Clicking...
[Recorder] Clicking visible menu link (Pricing)...
[Job 1] Streaming video upload to Cloudinary...
[Job 1] Saved to MongoDB!
[Job 1] Completed successfully!
At that point, the entire chain—from the frontend on my phone to the backend, queue, worker, browser automation, video pipeline, and database—is working together.
What This Setup Shows
Resource Optimization: I don't have to run the application's resource-intensive environment on my phone. Codespaces handles the databases, worker, Chromium, and application servers, while my phone is mainly used for editing, terminal access, and interacting with the running application.
Less Environment Management: Without Docker and
screen, I'd have to install and manage each service individually and keep separate terminal sessions open for the frontend, backend, worker, Redis, and MongoDB. Docker handles the data stores, whilescreenkeeps the application processes running independently.The Constraint Changes the Workflow, Not Necessarily the Work: A phone obviously isn't as comfortable as a full workstation, but with the right tooling, it doesn't prevent me from working on a multi-service application.
The setup isn't conventional, but it works, and this is what a few hours of software engineering looked like for me today.
Top comments (0)