The road to production
Tests written after the fact, Docker images that built by skipping the type checker, and the weekend RedCheck went from my laptop to a real server.
By the start of April RedCheck did everything I’d set out to build: the notebook’s daily list, recurring tasks, a heatmap, a calendar and an AI that planned your day (part 4). It ran on one machine, mine. This last part of the beginnings is about what it took to make it run anywhere else.
Tests, after the fact
I’ll be honest about the order: the tests came after the code. The whole suite went in on a single day, 31 March, once the backend had stopped changing shape.
- Service tests with Mockito. Repositories are replaced by mocks, so the business logic runs without a database: the recurring-task scheduler deciding whether to generate a task, the daily progress snapshot, the ownership checks.
- Controller tests with MockMvc. Full HTTP requests against the API, checking status codes, the JSON that comes back and that the JWT filter lets the right requests through.
- JaCoCo to measure what all of that actually covered.


The reports tell the truth, and the truth has a red stripe: everything AI-related sat at 0 %. It was the newest code, the least stable, and the hardest to test, because its output comes from a model. I knew that and moved on. It’s also, in hindsight, exactly where the worst bug in RedCheck’s history was hiding: the RAG pipeline that never wrote anything (part 11).
Writing tests after the fact still paid off in a way I didn’t expect. Testing every endpoint one by one made me look at each of them, and two turned out to be unused: fetching a single task or a single recurring task by id. The frontend never called them, so they were deleted the same day. Less code, less to secure.
Containers
On 6 and 7 April both halves got a Dockerfile. The backend’s builds the Spring Boot application into an image. The frontend’s builds the React app with Node and then serves the static files with Nginx. Two images, each runnable anywhere with Docker.
One commit from those days deserves a mention. Its message says
simplify build script. What it actually did was change tsc -b && vite build to just vite build: the production build stopped running the
TypeScript compiler, because it was failing (six errors, I checked) and
the image wouldn’t build. It worked, in the sense that the image built. It also
meant type errors could pile up unnoticed, and they did: when I reviewed
the code before going open source, the frontend had 11 TypeScript errors
(part 10). Skipping a check that fails
doesn’t fix anything. It just moves the bill.
Learning to deploy
Then I put the project down for a while. When I picked it up again at the end of May, it was with a different question. Deploying RedCheck had never been part of the plan: the app was finished as far as I was concerned. But I’d never put a real application on the internet myself, and I wanted to know how that actually works.
That weekend, 30 May to 1 June, was a crash course:
- Configuration out of the code. Database credentials and other values that had been hardcoded for local development moved to environment variables. On a laptop, hardcoding is convenient. On a server, it’s a leak waiting to happen.
- CORS, properly. The allowed origins became a setting instead of a
development shortcut. What I didn’t notice was that every controller
still carried its own
@CrossOrigin("*")from day one. That one survived until the open-source review months later. - A demo instead of sign-ups. I wasn’t ready to handle real users’ data, so registration was switched off and replaced by a demo account, created automatically at startup, and a one-click “quick demo access” button on the login screen. The demo account can’t be deleted, so the first visitor can’t ruin it for the next.
- One origin. Until then the frontend called the backend directly at
localhost:8080. In production, Nginx started forwarding API requests to the backend, so the browser only ever talks to one domain. - HTTPS, and two broken features. After switching to HTTPS, the
heatmap and the AI stopped working while everything else was fine. The
cause was in that new Nginx config: instead of forwarding everything
under one API prefix, it listed each backend path by name, and
/progressand/aiweren’t on the list. Two one-line fixes, and a lesson about configuration that has to be kept in sync with the code by hand.
What I took from the beginnings
Looking back at these five parts:
- A habit made a better spec than any requirements document I could have written from scratch.
- Building the backend first was the right call for one person. The interface took ten days because the API never made it wait.
- The shortcuts were never free. The 0 % coverage on the AI, the skipped
type checker and the forgotten
@CrossOrigin("*")all came back, months later, as work. - Deploying something teaches you things building it never will.
On 2 June 2026 RedCheck went live on its own domain. That’s where the next stage of this series begins: RedCheck goes live, the first part of Out in the Wild.
A principios de abril RedCheck hacía todo lo que me había propuesto: la lista diaria de la libreta, tareas recurrentes, un mapa de calor, un calendario y una IA que te planificaba el día (parte 4). Funcionaba en una sola máquina, la mía. Esta última parte de los inicios cuenta lo que hizo falta para que funcionara en cualquier otra.
Tests, a posteriori
Voy a ser sincero con el orden: los tests llegaron después del código. Toda la batería entró en un solo día, el 31 de marzo, cuando el backend ya había dejado de cambiar de forma.
- Tests de servicios con Mockito. Los repositorios se sustituyen por mocks, así que la lógica de negocio se ejecuta sin base de datos: el proceso que decide si generar una tarea recurrente, la foto diaria del progreso, las comprobaciones de propiedad.
- Tests de controladores con MockMvc. Peticiones HTTP completas contra la API, comprobando códigos de estado, el JSON que vuelve y que el filtro JWT deja pasar las peticiones correctas.
- JaCoCo para medir qué cubría realmente todo eso.


Los informes dicen la verdad, y la verdad tiene una franja roja: todo lo relacionado con la IA estaba al 0 %. Era el código más nuevo, el menos estable y el más difícil de probar, porque su salida viene de un modelo. Lo sabía y seguí adelante. Visto ahora, es exactamente donde se escondía el peor bug de la historia de RedCheck: el pipeline de RAG que nunca escribía nada (parte 11).
Escribir los tests a posteriori compensó de una forma que no esperaba. Probar cada endpoint uno a uno me obligó a mirarlos todos, y dos resultaron no usarse: obtener una sola tarea o una sola tarea recurrente por id. El frontend nunca los llamaba, así que se borraron ese mismo día. Menos código, menos que proteger.
Contenedores
El 6 y el 7 de abril las dos mitades tuvieron su Dockerfile. El del backend compila la aplicación de Spring Boot en una imagen. El del frontend compila la app de React con Node y después sirve los archivos estáticos con Nginx. Dos imágenes, cada una ejecutable en cualquier sitio con Docker.
Un commit de esos días merece mención. Su mensaje dice simplify build
script. Lo que hizo en realidad fue cambiar tsc -b && vite build por
solo vite build: el build de producción dejó de ejecutar el compilador
de TypeScript, porque fallaba (seis errores, lo he comprobado) y la
imagen no se generaba. Funcionó, en el sentido de que la imagen se
generó. También significó que los errores de tipos podían acumularse sin
que nadie se diera cuenta, y se acumularon: cuando revisé el código antes
de publicarlo como código abierto, el frontend tenía 11 errores de
TypeScript (parte 10). Saltarse una
comprobación que falla no arregla nada. Solo aplaza la factura.
Aprender a desplegar
Después aparqué el proyecto una temporada. Cuando lo retomé a finales de mayo, fue con otra pregunta. Desplegar RedCheck nunca había estado en el plan: para mí, la app estaba terminada. Pero nunca había puesto yo mismo una aplicación real en internet, y quería saber cómo funciona eso de verdad.
Aquel fin de semana, del 30 de mayo al 1 de junio, fue un curso intensivo:
- La configuración, fuera del código. Las credenciales de la base de datos y otros valores que estaban escritos en el código para el desarrollo en local pasaron a variables de entorno. En un portátil, escribirlos en el código es cómodo. En un servidor, es una filtración esperando a ocurrir.
- CORS, en serio. Los orígenes permitidos pasaron a ser un ajuste en
lugar de un atajo de desarrollo. Lo que no vi fue que cada controlador
seguía llevando su propio
@CrossOrigin("*")desde el primer día. Ese sobrevivió hasta la revisión para el código abierto, meses después. - Una demo en lugar de registros. No estaba preparado para gestionar datos de usuarios reales, así que desactivé el registro y lo sustituí por una cuenta demo, creada automáticamente al arrancar, y un botón de “acceso rápido a la demo” en la pantalla de login. La cuenta demo no se puede borrar, para que el primer visitante no se la estropee al siguiente.
- Un solo origen. Hasta entonces el frontend llamaba al backend
directamente en
localhost:8080. En producción, Nginx empezó a reenviar las peticiones de la API al backend, así que el navegador solo habla con un dominio. - HTTPS, y dos funcionalidades rotas. Al pasar a HTTPS, el mapa de
calor y la IA dejaron de funcionar mientras todo lo demás iba bien. La
causa estaba en esa nueva configuración de Nginx: en lugar de reenviar
todo lo que cuelga de un prefijo de API, enumeraba cada ruta del
backend por su nombre, y
/progressy/aino estaban en la lista. Dos arreglos de una línea, y una lección sobre la configuración que hay que mantener sincronizada con el código a mano.
Con qué me quedo de los inicios
Mirando atrás a estas cinco partes:
- Una costumbre fue mejor especificación que cualquier documento de requisitos que hubiera podido escribir desde cero.
- Construir primero el backend fue lo acertado para una sola persona. La interfaz llevó diez días porque la API nunca la hizo esperar.
- Los atajos nunca salieron gratis. El 0 % de cobertura de la IA, el
comprobador de tipos desactivado y el
@CrossOrigin("*")olvidado volvieron todos, meses después, en forma de trabajo. - Desplegar algo te enseña cosas que construirlo nunca te enseñará.
El 2 de junio de 2026 RedCheck salió a producción con su propio dominio. Ahí empieza la siguiente etapa de esta serie: RedCheck en producción, la primera parte de Ya en el mundo real.
- Backend
- DevOps