<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Norman Torres</title>
    <description>The latest articles on DEV Community by Norman Torres (@norman404).</description>
    <link>https://dev.to/norman404</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F801475%2Fda09e391-4ff2-4af1-aec9-31daea1c88a1.jpg</url>
      <title>DEV Community: Norman Torres</title>
      <link>https://dev.to/norman404</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/norman404"/>
    <language>en</language>
    <item>
      <title>El cuello de botella que es la base de datos (1k-10k)</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Thu, 19 Mar 2026 05:01:15 +0000</pubDate>
      <link>https://dev.to/norman404/el-cuello-de-botella-que-es-la-base-de-datos-1k-10k-2d4l</link>
      <guid>https://dev.to/norman404/el-cuello-de-botella-que-es-la-base-de-datos-1k-10k-2d4l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Esto es fantasía (Parte 3).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lanzamos el 1 de enero de 2026. En febrero separamos la infraestructura: base de datos en RDS, dos instancias detrás de un load balancer, y el sistema voló. Se sentía como el día del lanzamiento. Para marzo, llegamos a los 10,000 usuarios únicos al mes.&lt;/p&gt;

&lt;p&gt;Y entonces la base de datos empezó a arder.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma
&lt;/h2&gt;

&lt;p&gt;PostgreSQL alcanzaba el 100% de CPU durante horas. La memoria se disparaba sin control. Los queries que antes respondían en milisegundos empezaban a acumular segundos. En horas pico, era una bola de nieve: muchos usuarios generando consultas pesadas al mismo tiempo, cada una más lenta que la anterior, hasta que el sistema colapsaba.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La solución de emergencia:&lt;/strong&gt; reiniciar la base de datos. Hasta 20 minutos de inactividad total mientras RDS se recuperaba.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La solución de fuerza bruta:&lt;/strong&gt; escalar la instancia. Funcionó unos días, como siempre. Pero volvíamos al punto de partida porque el problema no era el tamaño del servidor, sino cómo lo estábamos usando.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Por qué crecía tan rápido?
&lt;/h3&gt;

&lt;p&gt;Nuestra app tiene una particularidad: un usuario nuevo no empieza en cero. Al registrarse, conecta sus cuentas bancarias y el sistema importa tarjetas, movimientos y balances desde el primer segundo. Un solo registro puede significar miles de inserciones.&lt;/p&gt;

&lt;p&gt;Más usuarios → más datos desde el día uno → más presión sobre la base de datos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paso 1: Índices
&lt;/h2&gt;

&lt;p&gt;Cuando analizamos los queries lentos, descubrimos algo vergonzoso: solo el &lt;code&gt;id&lt;/code&gt; estaba indexado. Pero las búsquedas reales las hacíamos por &lt;code&gt;fecha&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt; y &lt;code&gt;account_id&lt;/code&gt;. Sin índices en esos campos, cada consulta hacía un &lt;strong&gt;sequential scan&lt;/strong&gt; — recorría la tabla entera.&lt;/p&gt;

&lt;p&gt;Con una base de datos en crecimiento constante, eso es insostenible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Antes: sequential scan en cada consulta&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'abc-123'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Seq Scan on transactions  (cost=0.00..45892.00 rows=234 width=128)&lt;/span&gt;
&lt;span class="c1"&gt;-- Execution Time: 1,842.531 ms&lt;/span&gt;

&lt;span class="c1"&gt;-- Después: creamos los índices que faltaban&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_transactions_account_date&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_username&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_accounts_user_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Resultado: index scan&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'abc-123'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Index Scan using idx_transactions_account_date  (cost=0.42..18.67 rows=234 width=128)&lt;/span&gt;
&lt;span class="c1"&gt;-- Execution Time: 2.341 ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De 1,842ms a 2ms. La mejora fue inmediata y brutal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; Los índices no son una optimización prematura. Son lo mínimo que necesitás para que una base de datos funcione en producción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paso 2: Separar lecturas de escrituras
&lt;/h2&gt;

&lt;p&gt;Con los índices resueltos, apareció otro problema. Cuando un usuario se registraba e importaba sus datos financieros, la base de datos se frenaba para todos.&lt;/p&gt;

&lt;p&gt;Analizamos el patrón de tráfico y encontramos una proporción de &lt;strong&gt;1:1,000&lt;/strong&gt; — por cada escritura, había casi mil lecturas. El problema es que las inserciones no solo escriben datos: también actualizan los índices y ocasionalmente disparan un rebalanceo del B-tree. Mientras eso pasa, las lecturas esperan.&lt;/p&gt;

&lt;p&gt;La solución: un &lt;strong&gt;cluster de RDS con réplicas de lectura&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          ┌──────────────────────┐
                          │   RDS Writer          │
                          │   (db.r6g.xlarge)     │
                          │                       │
                          │   INSERT / UPDATE      │
                          └──────────▲─────────────┘
                                     │
                              Replicación
                              asíncrona
                                     │
              ┌──────────────────────┼──────────────────────┐
              │                      │                      │
     ┌────────▼─────────┐  ┌────────▼─────────┐  ┌────────▼─────────┐
     │  Reader 1        │  │  Reader 2        │  │  Reader 3        │
     │  (db.r6g.medium) │  │  (db.r6g.medium) │  │  (db.r6g.medium) │
     │  SELECT (reportes)│  │  SELECT (app)    │  │  SELECT (app)    │
     └──────────────────┘  └──────────────────┘  └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En la API, la implementación es directa. Dos conexiones, una para cada rol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// datasource.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writerPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RDS_WRITER_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// finanzas-db.cluster-cxyz.us-west-2.rds.amazonaws.com&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readerPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RDS_READER_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// finanzas-db.cluster-ro-cxyz.us-west-2.rds.amazonaws.com&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writerPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// INSERT, UPDATE, DELETE&lt;/span&gt;
  &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;readerPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// SELECT&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Uso en la API&lt;/span&gt;
&lt;span class="c1"&gt;// Lectura → va a las réplicas&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM transactions WHERE account_id = $1 AND date &amp;gt;= $2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Escritura → va al writer&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT INTO transactions (account_id, amount, date) VALUES ($1, $2, $3)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RDS distribuye automáticamente las lecturas entre las réplicas. El resultado: las inserciones masivas de un registro nuevo ya no bloquean las consultas de los 9,999 usuarios restantes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paso 3: Connection Pooling
&lt;/h2&gt;

&lt;p&gt;Con más servicios conectándose a la base de datos, empezamos a ver bloqueos que no tenían sentido. El CPU estaba en 20%, la memoria tranquila, pero la base de datos no respondía.&lt;/p&gt;

&lt;p&gt;El problema: las conexiones. PostgreSQL crea un proceso por cada conexión. Nuestros servicios abrían conexiones, las mantenían activas mientras procesaban la respuesta HTTP, y las nuevas solicitudes se quedaban esperando porque PostgreSQL había alcanzado su límite de conexiones (&lt;code&gt;max_connections&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Implementamos &lt;strong&gt;PgBouncer&lt;/strong&gt; como connection pooler entre la API y RDS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────┐      ┌────────────┐      ┌──────────┐
│   API     │─────▶│  PgBouncer │─────▶│   RDS    │
│ (100 conn)│      │  (20 conn) │      │          │
└───────────┘      └────────────┘      └──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La API puede abrir 100 conexiones contra PgBouncer, pero PgBouncer solo mantiene 20 conexiones reales contra PostgreSQL. Cuando un proceso termina de usar una conexión, PgBouncer la recicla para el siguiente en la cola.&lt;/p&gt;

&lt;p&gt;Menos conexiones activas → menos procesos en PostgreSQL → menos memoria y CPU desperdiciados en overhead de conexión.&lt;/p&gt;

&lt;h2&gt;
  
  
  El costo del crecimiento
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concepto&lt;/th&gt;
&lt;th&gt;Costo mensual (estimado)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2x EC2 t3.small (API)&lt;/td&gt;
&lt;td&gt;~$30.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RDS Writer (db.r6g.xlarge)&lt;/td&gt;
&lt;td&gt;~$180.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3x RDS Reader (db.r6g.medium)&lt;/td&gt;
&lt;td&gt;~$135.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;~$20.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Transfer &amp;amp; Storage&lt;/td&gt;
&lt;td&gt;~$15.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$380 - $400 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;De $25 a $85 a $400. El salto es grande, pero la alternativa era seguir reiniciando la base de datos en horas pico y perdiendo usuarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendimos
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Los índices no son opcionales.&lt;/strong&gt; Si hacés queries por un campo, ese campo necesita un índice. Es así de simple.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leer y escribir son problemas diferentes.&lt;/strong&gt; Separarlos te da control sobre cada uno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Las conexiones son un recurso finito.&lt;/strong&gt; Connection pooling no es una optimización: es una necesidad a partir de cierta escala.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalar verticalmente es un parche.&lt;/strong&gt; Comprar más CPU aplaza el problema. Entender el problema lo resuelve.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ¿Qué sigue?
&lt;/h2&gt;

&lt;p&gt;Con 10,000 usuarios, la base de datos respira. Pero hay algo que no tiene sentido: el dashboard de un usuario muestra los mismos datos todo el día — el balance, las últimas transacciones, los presupuestos — y cada vez que abre la app, le pegamos a la base de datos como si fuera la primera vez. Multiplicá eso por miles de usuarios en hora pico y estamos consultando lo mismo una y otra vez. La base de datos ya no es lenta, pero le estamos pidiendo trabajo que no necesita hacer.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Separando Responsabilidades (100-1,000 usuarios)</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Fri, 20 Feb 2026 05:37:38 +0000</pubDate>
      <link>https://dev.to/norman404/separando-responsabilidades-100-1000-usuarios-21f</link>
      <guid>https://dev.to/norman404/separando-responsabilidades-100-1000-usuarios-21f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Esto es fantasía (Parte 2).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lanzamos el 1 de Enero de 2026. Para inicios de Febrero, el crecimiento fue exponencial: llegamos a los 1,000 usuarios únicos al mes.&lt;/p&gt;

&lt;p&gt;Cuando teníamos 100 usuarios, nuestra humilde infraestructura (una sola instancia EC2 corriendo todo) soportaba la carga sin despeinarse. Pero al cruzar la barrera de los 1,000, la realidad nos golpeó. La base de datos y la API empezaron a competir salvajemente por la CPU y la RAM de la instancia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El síntoma:&lt;/strong&gt; Si un usuario generaba un reporte pesado, la base de datos consumía el 100% del CPU. Resultado: La API dejaba de responder a &lt;em&gt;todos&lt;/em&gt; los demás usuarios. Tiempos de respuesta de 200ms pasaron a 15 segundos o timeouts.&lt;/p&gt;

&lt;p&gt;Nuestra primera reacción fue "fuerza bruta": escalar verticalmente a una instancia más grande (t3.medium). Funcionó... por tres días. El problema de fondo persistía: acoplamiento de recursos.&lt;/p&gt;

&lt;p&gt;Decidimos que era hora de madurar la arquitectura.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paso 1: Desacoplando la Base de Datos (RDS)
&lt;/h2&gt;

&lt;p&gt;Mover la base de datos fuera de nuestro servidor fue la prioridad. Migramos de un contenedor Docker local a &lt;strong&gt;AWS RDS (Amazon Relational Database Service)&lt;/strong&gt; usando Postgres.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Por qué?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recursos Dedicados:&lt;/strong&gt; La API ya no pelea por CPU con la DB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estabilidad:&lt;/strong&gt; Si la API crashea por un bug, la DB sigue viva.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mantenimiento:&lt;/strong&gt; Backups automáticos y actualizaciones gestionadas por AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  El Cambio
&lt;/h3&gt;

&lt;p&gt;En nuestro &lt;code&gt;docker-compose.yml&lt;/code&gt;, eliminamos el servicio &lt;code&gt;db&lt;/code&gt; y actualizamos la configuración de la API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (Actualizado)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;turegistro/api:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
      &lt;span class="c1"&gt;# Ahora apuntamos al endpoint de RDS&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://admin:password_super_seguro@finanzas-db.cluster-cxyz.us-west-2.rds.amazonaws.com:5432/finanzas&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PORT=3000&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
    &lt;span class="c1"&gt;# Ya no dependemos de un servicio local 'db'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Paso 2: Alta Disponibilidad (Load Balancer)
&lt;/h2&gt;

&lt;p&gt;Con la DB separada, notamos otro problema: cada vez que hacíamos un deploy o reiniciábamos el servidor, el servicio se caía por completo durante unos segundos (o minutos). Además, si esa única instancia EC2 fallaba, estábamos fuera del aire.&lt;/p&gt;

&lt;p&gt;No queríamos soluciones parches como &lt;code&gt;api2.midominio.com&lt;/code&gt;. Queríamos transparencia.&lt;/p&gt;

&lt;p&gt;Implementamos un &lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt;.&lt;br&gt;
El ALB funciona como un policía de tráfico: recibe todas las peticiones y las distribuye entre nuestros servidores disponibles.&lt;/p&gt;
&lt;h3&gt;
  
  
  La Nueva Arquitectura
&lt;/h3&gt;

&lt;p&gt;Ahora tenemos 2 instancias EC2 idénticas (para redundancia) y una base de datos externa.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                  ┌───────────────────┐
                                  │    AWS RDS        │
                                  │  (PostgreSQL)     │
                                  └─────────▲─────────┘
                                            │
                                     ┌──────┴──────┐
                                     │             │
                    ┌───────────────▶│ Instancia A │
┌──────────┐        │                │ (API Docker)│
│ Usuario  │──HTTPS─┼─▶  ALB  ──────▶└─────────────┘
└──────────┘        │  (Balanceador)
                    │                ┌─────────────┐
                    └───────────────▶│ Instancia B │
                                     │ (API Docker)│
                                     └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si la &lt;strong&gt;Instancia A&lt;/strong&gt; muere, el ALB automáticamente manda todo el tráfico a la &lt;strong&gt;Instancia B&lt;/strong&gt;. El usuario ni se entera.&lt;/p&gt;

&lt;h2&gt;
  
  
  Networking y Seguridad (VPC)
&lt;/h2&gt;

&lt;p&gt;Aquí es donde las cosas se pusieron serias. Tuvimos que configurar correctamente nuestros &lt;strong&gt;Security Groups&lt;/strong&gt; para no dejar nada expuesto.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Group del ALB:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Inbound:&lt;/em&gt; Permite tráfico 80/443 desde todo el mundo (&lt;code&gt;0.0.0.0/0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Outbound:&lt;/em&gt; Solo hacia el Security Group de las EC2.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Group de las EC2 (App):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Inbound:&lt;/em&gt; &lt;strong&gt;SOLO&lt;/strong&gt; permite tráfico en el puerto 3000 proveniente del Security Group del ALB. Nadie puede conectarse directo a la IP de la instancia (excepto nosotros por SSH).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Group de RDS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Inbound:&lt;/em&gt; &lt;strong&gt;SOLO&lt;/strong&gt; permite tráfico en el puerto 5432 proveniente del Security Group de las EC2.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Resultado: La base de datos es invisible desde internet. Las instancias son invisibles desde internet (solo el ALB les habla).&lt;/p&gt;

&lt;h2&gt;
  
  
  El Costo del Crecimiento
&lt;/h2&gt;

&lt;p&gt;La "fantasía" de los $25 USD se termina aquí. La redundancia y los servicios gestionados cuestan.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concepto&lt;/th&gt;
&lt;th&gt;Costo Mensual (Estimado)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2x EC2 t3.small&lt;/td&gt;
&lt;td&gt;~$30.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS RDS (db.t3.micro)&lt;/td&gt;
&lt;td&gt;~$18.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;~$16.00 USD + tráfico&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Transfer &amp;amp; Storage&lt;/td&gt;
&lt;td&gt;~$10.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$75 - $85 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pasamos de gastar lo de una cena barata a pagar una suscripción de software empresarial. Pero a cambio, ganamos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Resiliencia:&lt;/strong&gt; Podemos perder un servidor y seguir operando.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Escalabilidad:&lt;/strong&gt; ¿Más usuarios? Agregamos una tercera instancia EC2 al balanceador y listo.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Paz mental:&lt;/strong&gt; Ya no reiniciamos servidores los domingos a la noche.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Separar responsabilidades es el primer paso real hacia una arquitectura distribuida. Aumentamos la complejidad y el costo, sí, pero compramos estabilidad.&lt;/p&gt;

&lt;p&gt;¿Qué sigue? Con 1,000 usuarios, las consultas de reportes siguen siendo lentas aunque la DB esté separada.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>spanish</category>
    </item>
    <item>
      <title>El MVP que funciona (1-100 usuarios)</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Thu, 15 Jan 2026 17:40:48 +0000</pubDate>
      <link>https://dev.to/norman404/el-mvp-que-funciona-1-100-usuarios-3pi3</link>
      <guid>https://dev.to/norman404/el-mvp-que-funciona-1-100-usuarios-3pi3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Esto es fantasía.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El 1° de enero lanzamos una plataforma de gestión financiera personal. Conecta cuentas bancarias, categoriza gastos, establece presupuestos y genera insights. Ese mismo día tuvimos nuestro primer usuario. La meta es llegar a 100 este mes.&lt;/p&gt;

&lt;p&gt;Este post es sobre la infraestructura detrás de ese MVP. No sobre código, arquitectura de software, ni patrones de diseño. Sobre servidores, contenedores, costos, y las decisiones pragmáticas que te permiten lanzar algo real con ~$25 USD al mes.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend Web:    React
Mobile:          React Native
Backend:         NestJS
Base de datos:   PostgreSQL
Contenedores:    Docker + Docker Compose
Servidor:        AWS EC2 + Nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nada exótico. Tecnologías probadas que cualquier developer puede mantener.&lt;/p&gt;

&lt;h2&gt;
  
  
  La arquitectura
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                            ┌───────────────────────────────────────────────┐
                            │               EC2 t3.small                    │
                            │                                               │
┌──────────┐                │  ┌─────────────────────────────────────────┐  │
│ Usuario  │───HTTPS:443───▶│  │            Docker Network               │  │
│ (Web)    │                │  │                                         │  │
└──────────┘                │  │  ┌───────┐   ┌───────┐   ┌──────────┐   │  │
                            │  │  │ Nginx │──▶│  API  │──▶│ Postgres │   │  │
┌──────────┐                │  │  │  :80  │   │ :3000 │   │  :5432   │   │  │
│ Usuario  │───HTTPS:443───▶│  │  │ :443  │   └───────┘   └──────────┘   │  │
│ (Mobile) │                │  │  └───────┘                              │  │
└──────────┘                │  │                                         │  │
                            │  └─────────────────────────────────────────┘  │
                            └───────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Todo en una instancia EC2, pero cada servicio aislado en su contenedor. Un &lt;code&gt;docker compose up -d&lt;/code&gt; y todo corre.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Por qué Docker para un MVP?
&lt;/h2&gt;

&lt;p&gt;La respuesta corta: porque facilita la vida.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beneficios clave
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistencia entre ambientes:&lt;/strong&gt; Lo que corre en mi laptop corre igual en producción. Adiós "en mi máquina funciona".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Despliegues rápidos y predecibles:&lt;/strong&gt; Actualizar la API es un &lt;code&gt;docker compose pull&lt;/code&gt; y &lt;code&gt;docker compose up -d&lt;/code&gt;. Sin sorpresas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aislamiento de servicios:&lt;/strong&gt; La base de datos no contamina el sistema host. Si algo falla, solo afecta su contenedor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidad futura:&lt;/strong&gt; Cuando el MVP crezca, migrar a múltiples servidores o servicios gestionados será más sencillo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilidad para nuevos desarrolladores:&lt;/strong&gt; Un nuevo dev solo necesita Docker y el repo. Nada de instalar PostgreSQL localmente o configurar variables de entorno complicadas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;turegistro/api:latest&lt;/span&gt;
    &lt;span class="c1"&gt;# build: ./api  # Para desarrollo local&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finanzas-api&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://usuario:password@db:5432/finanzas&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PORT=3000&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finanzas-db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=usuario&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=finanzas&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./backup:/backup&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;usuario&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;finanzas"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;

  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;finanzas-nginx&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/conf.d:/etc/nginx/conf.d:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/ssl:/etc/nginx/ssl:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./frontend/dist:/var/www/app:ro&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Detalles importantes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;restart: unless-stopped&lt;/code&gt;&lt;/strong&gt; Si el contenedor crashea, Docker lo reinicia automáticamente. Si yo lo detengo manualmente, no lo reinicia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;depends_on&lt;/code&gt; con &lt;code&gt;condition: service_healthy&lt;/code&gt;&lt;/strong&gt; La API no inicia hasta que PostgreSQL esté listo para aceptar conexiones. Evita errores de conexión en el startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volumen para PostgreSQL&lt;/strong&gt; &lt;code&gt;postgres_data&lt;/code&gt; persiste los datos fuera del contenedor. Si recreo el contenedor de Postgres, los datos sobreviven.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red interna&lt;/strong&gt; Los contenedores se comunican por nombre (&lt;code&gt;db&lt;/code&gt;, &lt;code&gt;api&lt;/code&gt;) dentro de la red &lt;code&gt;internal&lt;/code&gt;. PostgreSQL nunca está expuesto a internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  El servidor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Selección de instancia
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instancia:      t3.small
vCPUs:          2
RAM:            2 GB
Almacenamiento: 30 GB gp3
Región:         us-west-2 (Oregón)
SO:             Ubuntu 24.04 LTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;¿Por qué t3.small y no t3.micro?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La micro tiene 1GB de RAM. Docker ya consume ~100MB, PostgreSQL quiere ~256MB para buffers, la API otros ~200MB, Nginx es ligero pero suma. Con 1GB estás en el límite desde el arranque.&lt;/p&gt;

&lt;p&gt;Con 2GB hay espacio para crecer, caches, y evitar OOM kills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué instancia "burstable" (t3)?&lt;/strong&gt;&lt;br&gt;
Las t3 acumulan créditos de CPU cuando están idle y los gastan en picos. Un MVP tiene ráfagas de tráfico, no carga constante.&lt;/p&gt;

&lt;p&gt;Nuestro uso promedio es ~8% de CPU. Los créditos se acumulan más rápido de lo que los gastamos.&lt;/p&gt;
&lt;h2&gt;
  
  
  Costos reales
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concepto&lt;/th&gt;
&lt;th&gt;Costo mensual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EC2 t3.small (on-demand)&lt;/td&gt;
&lt;td&gt;~$15.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EBS 30GB gp3&lt;/td&gt;
&lt;td&gt;~$2.40 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data transfer (estimado)&lt;/td&gt;
&lt;td&gt;~$2-5 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dominio (anualizado)&lt;/td&gt;
&lt;td&gt;~$1.00 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Hub (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$20-25 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Optimizaciones que NO hicimos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reserved Instances:&lt;/strong&gt; Compromiso de 1-3 años. El MVP puede pivotar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR en lugar de Docker Hub:&lt;/strong&gt; Más control, pero Docker Hub free tier es suficiente para imágenes privadas limitadas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot Instances:&lt;/strong&gt; AWS puede terminarlas. No para producción.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Networking y seguridad
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Security Groups
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inbound:
  - 22 (SSH)      → Solo mi IP
  - 80 (HTTP)     → 0.0.0.0/0 (redirige a 443)
  - 443 (HTTPS)   → 0.0.0.0/0

Outbound:
  - All traffic   → 0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;PostgreSQL (5432) NO está expuesto. Solo existe dentro de la red de Docker. Para acceder remotamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Túnel SSH + docker exec&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/mi_llave.pem usuario@servidor
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; finanzas-db psql &lt;span class="nt"&gt;-U&lt;/span&gt; usuario &lt;span class="nt"&gt;-d&lt;/span&gt; finanzas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Variables de entorno
&lt;/h3&gt;

&lt;p&gt;Los secrets no van en el &lt;code&gt;docker-compose.yml&lt;/code&gt; del repo. En el servidor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# /opt/app/.env&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://usuario:password_real@db:5432/finanzas
&lt;span class="nv"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret_real
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y en el compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El archivo &lt;code&gt;.env&lt;/code&gt; está en &lt;code&gt;.gitignore&lt;/code&gt;. Cada ambiente tiene el suyo.&lt;br&gt;
Por ambiente me refiero a desarrollo local y producción.&lt;/p&gt;
&lt;h2&gt;
  
  
  Nginx con Docker
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Configuración
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;dominio.com&lt;/span&gt; &lt;span class="s"&gt;api.dominio.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;dominio.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;api.dominio.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://api&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; &lt;code&gt;server api:3000&lt;/code&gt; funciona porque Docker resuelve &lt;code&gt;api&lt;/code&gt; al contenedor de la API dentro de la red interna.&lt;/p&gt;
&lt;h3&gt;
  
  
  SSL con Let's Encrypt
&lt;/h3&gt;

&lt;p&gt;Certbot corre en el host (no en contenedor) para simplicidad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certonly &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; tudominio.com &lt;span class="nt"&gt;-d&lt;/span&gt; api.tudominio.com

&lt;span class="c"&gt;# Copiar certificados donde Nginx los espera&lt;/span&gt;
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/letsencrypt/live/tudominio.com/fullchain.pem /opt/app/nginx/ssl/
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/letsencrypt/live/tudominio.com/privkey.pem /opt/app/nginx/ssl/

&lt;span class="c"&gt;# Reiniciar Nginx para que tome los nuevos certs&lt;/span&gt;
docker compose restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Renovación automática via cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/cron.d/certbot-renew&lt;/span&gt;
0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; root certbot renew &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--post-hook&lt;/span&gt; &lt;span class="s2"&gt;"cp /etc/letsencrypt/live/tudominio.com/*.pem /opt/app/nginx/ssl/ &amp;amp;&amp;amp; docker compose -f /opt/app/docker-compose.yml restart nginx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Proceso actual
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Build y push de la imagen (local)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; turegistro/api:latest ./api
docker push turegistro/api:latest

&lt;span class="c"&gt;# 2. Build del frontend&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;frontend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build

&lt;span class="c"&gt;# 3. En el servidor&lt;/span&gt;
ssh usuario@servidor
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/app

&lt;span class="c"&gt;# 4. Pull de la nueva imagen y restart&lt;/span&gt;
docker compose pull api
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; api

&lt;span class="c"&gt;# 5. Actualizar frontend (rsync desde local)&lt;/span&gt;
rsync &lt;span class="nt"&gt;-avz&lt;/span&gt; &lt;span class="nt"&gt;--delete&lt;/span&gt; frontend/dist/ usuario@servidor:/opt/app/frontend/dist/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rollback
&lt;/h3&gt;

&lt;p&gt;Si algo sale mal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver imágenes disponibles&lt;/span&gt;
docker images turegistro/api

&lt;span class="c"&gt;# Volver a versión anterior&lt;/span&gt;
docker compose down
docker tag turegistro/api:previous turegistro/api:latest
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Zero-downtime deploy (futuro)
&lt;/h3&gt;

&lt;p&gt;Por ahora hay ~5 segundos de downtime en cada deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backups
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Base de datos
&lt;/h3&gt;

&lt;p&gt;Script de backup que corre dentro del contenedor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# /opt/app/scripts/backup.sh&lt;/span&gt;
&lt;span class="nv"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;finanzas-db pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; usuario &lt;span class="nt"&gt;-Fc&lt;/span&gt; finanzas &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /opt/app/backup/db_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.dump

&lt;span class="c"&gt;# Mantener solo últimos 7 días&lt;/span&gt;
find /opt/app/backup &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"db_*.dump"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +7 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron en el host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/cron.d/db-backup&lt;/span&gt;
0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; root /opt/app/scripts/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Subir a S3
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Agregar al script de backup&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; /opt/app/backup/db_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.dump s3://tu-bucket/backups/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker volumes
&lt;/h3&gt;

&lt;p&gt;El volumen &lt;code&gt;postgres_data&lt;/code&gt; vive en &lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt;. Si el servidor muere, los datos mueren con él. Por eso el backup a S3 es importante.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoreo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Uptime
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UptimeRobot&lt;/strong&gt; (gratis):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ping a &lt;code&gt;https://api.dominio.com/health&lt;/code&gt; cada 5 min&lt;/li&gt;
&lt;li&gt;Alerta por email/Telegram si no responde&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Logs
&lt;/h3&gt;

&lt;p&gt;Docker centraliza los logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Logs de todos los servicios&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Solo la API, últimas 100 líneas&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 api

&lt;span class="c"&gt;# Logs de un período específico&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;--since&lt;/span&gt; 2024-01-15T10:00:00 api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lo que NO tenemos
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Servicio&lt;/th&gt;
&lt;th&gt;Por qué no&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Un servidor, tres contenedores. &lt;code&gt;docker compose&lt;/code&gt; es suficiente.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CDN (CloudFront)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frontend de ~500KB, usuarios en México. Latencia imperceptible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load Balancer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Un servidor. Nada que balancear.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sin cache. Queries directas a Postgres. Dataset pequeño.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RDS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cuesta lo mismo que toda la infra actual.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECS/Fargate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Overhead de configuración sin beneficio para esta escala.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Un servidor, un compose file. Lo documento en el README.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI/CD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1-2 deploys por semana. El proceso manual toma 3 minutos.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ¿Qué va a romper primero?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Disco lleno
&lt;/h3&gt;

&lt;p&gt;Logs de Docker crecen. Imágenes viejas se acumulan. Backups suman.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Señales:&lt;/strong&gt; Alertas de disco &amp;gt;80%, contenedores que no inician.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. RAM insuficiente
&lt;/h3&gt;

&lt;p&gt;Más usuarios = más conexiones = más memoria por contenedor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Señales:&lt;/strong&gt; Contenedores reiniciando (OOM killed), &lt;code&gt;docker stats&lt;/code&gt; mostrando &amp;gt;90% de memoria.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. La DB necesita su propio servidor
&lt;/h3&gt;

&lt;p&gt;Cuando PostgreSQL y la API compiten por I/O.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Señales:&lt;/strong&gt; Query times subiendo, &lt;code&gt;docker stats&lt;/code&gt; mostrando I/O wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solución:&lt;/strong&gt; Mover el contenedor de Postgres a un segundo EC2, o migrar a RDS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comandos útiles del día a día
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver estado de los contenedores&lt;/span&gt;
docker compose ps

&lt;span class="c"&gt;# Reiniciar todo&lt;/span&gt;
docker compose restart

&lt;span class="c"&gt;# Reiniciar solo la API&lt;/span&gt;
docker compose restart api

&lt;span class="c"&gt;# Ver logs en tiempo real&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Entrar al contenedor de la DB&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; finanzas-db psql &lt;span class="nt"&gt;-U&lt;/span&gt; usuario &lt;span class="nt"&gt;-d&lt;/span&gt; finanzas

&lt;span class="c"&gt;# Entrar al contenedor de la API&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; finanzas-api sh

&lt;span class="c"&gt;# Rebuild sin cache (cuando algo está raro)&lt;/span&gt;
docker compose build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; api
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Docker añade una capa, pero es una capa que paga su costo. Deploys reproducibles, ambientes idénticos, y la tranquilidad de que &lt;code&gt;docker compose up -d&lt;/code&gt; va a funcionar igual hoy que en 6 meses.&lt;/p&gt;

&lt;p&gt;El setup completo: ~$25 USD/mes. Tres contenedores. Un servidor. Cero magia.&lt;/p&gt;

&lt;p&gt;La complejidad se agrega cuando duele. Y con Docker Compose en un solo EC2, hay mucho espacio antes de que duela.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Tenemos avances...</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Fri, 10 May 2024 18:32:58 +0000</pubDate>
      <link>https://dev.to/norman404/tenemos-avances-52fl</link>
      <guid>https://dev.to/norman404/tenemos-avances-52fl</guid>
      <description>&lt;p&gt;Ya llevo más de un mes en mi nuevo puesto, y hemos enfrentado numerosos desafíos; estos últimos 15 días han sido especialmente caóticos. No hemos podido avanzar tanto como me gustaría debido a que estamos atascados en una migración de uno de los sistemas vitales de la empresa, que, desde mi perspectiva, ha sido mal implementada. Antes de mi llegada, ya se había intentado realizar esta migración, pero al implementarla en producción, todo falló y tuvimos que revertirla. Esto es preocupante, ya que afecta directamente la generación de solicitudes para nuevos créditos. Además, la plataforma está innecesariamente complicada, con versiones obsoletas de tecnologías y adiciones mal implementadas, como el uso de GraphQL solo por el interés de aprenderlo.&lt;/p&gt;

&lt;p&gt;Además, perdí todo un día intentando recuperar una tabla de SQL que uno de los desarrolladores borró por accidente. Afortunadamente, pudimos restaurarla gracias a que teníamos copias de seguridad. Aunque la recuperación y la integración de los datos solo tomaron una hora, el proceso completo nos llevó cerca de cinco horas. Días como ese trastocan todos mis planes, ya que tengo que dedicar tiempo al soporte de la plataforma, a pesar de que no somos un equipo de soporte y no hay nadie más que pueda encargarse de esto.&lt;/p&gt;

&lt;p&gt;Lo interesante de estas últimas dos semanas es que logré implementar un despliegue automático. Ahora, cada vez que se sube un cambio a la rama master en GitHub, Jenkins inicia un proceso de construcción y sube los archivos a un bucket de S3, que luego distribuye la información a los usuarios. Esto me ha facilitado mucho el proceso de llevar código a producción sin grandes demoras, permitiéndome hacer entregas más frecuentes. Aunque todavía no tenemos un ambiente de desarrollo ni control de pruebas automáticas, es un gran avance. Logré un hito similar con una plataforma hermana de la empresa, que ahora cambia a un despliegue automático cada vez que se actualiza la rama principal en GitHub.&lt;/p&gt;

&lt;p&gt;Para manejar mejor la carga de trabajo, he aplicado una estrategía de planificación de metas. Cada lunes a las 9 am, me tomo unos 15 minutos para definir lo que quiero lograr esa semana. Me propongo tres proyectos fuera de la planificación del equipo, lo que me ayuda a priorizar mejor mis tareas. Continuaré aplicando esta estrategia para mantener un control sobre el progreso semanal. Otro logro reciente fue añadir documentación básica a los proyectos, en forma de archivos README.md, con instrucciones para realizar despliegues, ejecutar el código y las variables importantes para el funcionamiento de los proyectos.&lt;/p&gt;

</description>
      <category>newjob</category>
      <category>devops</category>
    </item>
    <item>
      <title>Primeras 3 semanas</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Tue, 23 Apr 2024 00:26:53 +0000</pubDate>
      <link>https://dev.to/norman404/primeras-3-semanas-13n6</link>
      <guid>https://dev.to/norman404/primeras-3-semanas-13n6</guid>
      <description>&lt;p&gt;Ya he cumplido mis primeras tres semanas de trabajo, lo cual ha sido bastante pesado, principalmente por el horario. Tener que despertar a las 6:30 a.m. y contar con un hijo que no permite mantener una hora constante para ir a dormir ha creado días en los que apenas logré dormir cuatro horas, pero estos son temas personales.&lt;/p&gt;

&lt;p&gt;En el lado del trabajo, se inició un proceso de implementación de buenas prácticas que no se ha podido completar al 100%, ya que existen muy malas prácticas arraigadas que complican y alargan el proceso de deploy. Un día, literalmente nos tomó cuatro horas subir unos simples cambios, y tuvimos un hotfix que terminó afectando tirando producción. Además, enfrentamos un problema con un servidor que alcanzó el 100% de uso de CPU, volviéndose inaccesible y provocando que toda la operación fallara. Esto nos obligó a crear otro servidor y, debido a la falta de buena documentación, el cambio no se pudo realizar correctamente, causando fallos en el sistema para casos particulares.&lt;/p&gt;

&lt;p&gt;El principal problema que enfrentamos ahora es la dificultad para subir cambios a producción de manera sencilla, lidiar con variables de entorno o ramas que contienen muchos más cambios de los esperados, y la incapacidad de utilizar completamente los recursos de pruebas debido a un cuello de botella en el ambiente de desarrollo. Además, la presencia de mucho código legacy sin soporte complica aún más la situación. Para terminar de empeorar todo esto, no puedo dedicar todo mi tiempo a resolver estos problemas porque tengo que realizar múltiples actividades, como revisiones de código, escribir el código de las historias de usuario que me corresponden, y apoyar al equipo en sus problemas y a los otros dos equipos que se tienen.&lt;/p&gt;

&lt;p&gt;Quisiera llegar a un punto donde los cambios sean tan simple de subir a producción y que no se rompa nada pero aun creo que falta mucho, por lo que voy a iniciar con una serie de actividades que ya estamos haciendo pero que no hemos podido llevar todo a buen puerto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sesiones de sincronización diaria de 15 minutos.&lt;/li&gt;
&lt;li&gt;Adopción de buenas practicas.&lt;/li&gt;
&lt;li&gt;Cultura de revisión de código.&lt;/li&gt;
&lt;li&gt;Esfuerzo de documentación: Readme y diagramas.&lt;/li&gt;
&lt;li&gt;Entregas continuas y pequeñas para minimizar los riesgos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Espero que en los siguientes días se vean cambios en la entrega de funcionalidades porque seguimos teniendo problemas con eso.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>trabajo</category>
    </item>
    <item>
      <title>Nuevo trabajo, nuevos retos</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Thu, 04 Apr 2024 19:53:55 +0000</pubDate>
      <link>https://dev.to/norman404/nuevo-trabajo-nuevos-retos-5hfo</link>
      <guid>https://dev.to/norman404/nuevo-trabajo-nuevos-retos-5hfo</guid>
      <description>&lt;p&gt;El 1° de abril de 2024 comencé a trabajar en una nueva empresa. Esta empresa tiene un mercado en expansión, pero su producto tecnológico no está orientado al usuario final, sino a los usuarios internos, lo cual me resulta un tanto curioso, ya que es más sencillo predecir su comportamiento y en caso de fallo, no afecta a tantos usuarios.&lt;/p&gt;

&lt;p&gt;Al ingresar a esta empresa, noté que enfrentan numerosos problemas, principalmente en cuanto a buenas prácticas de desarrollo, uso de metodologías ágiles e infraestructura.&lt;/p&gt;

&lt;p&gt;Cuando se desea desarrollar algo, el dueño de la empresa indica al equipo qué hacer y se involucra mucho en diferentes partes del proceso, lo que hace que la persona encargada realmente pierda su autoridad. Los desarrolladores prefieren hablar directamente con el dueño, quien deja muchos detalles de lo que se quiere realizar al aire, lo que hace necesario usar la intuición para completar las tareas, con el riesgo de que al final no sean lo que se esperaba. Además, el dueño cambia las prioridades del equipo con mucha frecuencia, lo que lleva a que lo que se planeó trabajar en tres días o menos deje de ser prioritario. Todo debe pasar por su autorización, lo que ralentiza los procesos de implementación de cambios.&lt;/p&gt;

&lt;p&gt;El equipo afirma que utilizan Scrum, pero solo llevan a cabo tres de las cinco ceremonias (daily, planning y review), dejando fuera el refinamiento y la retrospectiva. El encargado tampoco tiene una clara comprensión de estas ceremonias, lo que lleva a que las realicen de forma improvisada. Según lo que me han contado y lo que he visto, las dailys parecen más una reunión donde el encargado cambia las prioridades del día, y la review se convierte en una sesión de planificación para ver qué se trabajará en los siguientes 15 días.&lt;/p&gt;

&lt;p&gt;El equipo carece de una guía en cuanto a buenas prácticas de desarrollo, lo que hace que cada uno haga lo que cree correcto. Esto resulta en códigos escritos de manera inconsistente en el mismo proyecto, numerosas ramas en el repositorio de git cuyo propósito ya no se recuerda, archivos transpilados en el repositorio y variables de entorno también incluidas, entre otras cuestiones.&lt;/p&gt;

&lt;p&gt;En cuanto a la infraestructura, han optado por un enfoque más tradicional, utilizando servidores en AWS para ejecutar sus APIs y entregar el frontend. Sin embargo, cada servidor aloja varios proyectos, lo que significa que si uno falla, todos los proyectos se ven afectados. Sus despliegues consisten en conectarse al servidor, hacer un pull y reiniciar las aplicaciones, lo que puede ocasionar problemas si algún paquete de node en el archivo package.json no está actualizado. También tienen una instancia de base de datos muy grande para sus necesidades (2xlarge), incluso teniendo menos de 100 usuarios, lo que parece deberse a malas indexaciones en las bases de datos. Además, carecen de conocimientos sobre pruebas y de un proceso de CI/CD.&lt;/p&gt;

&lt;p&gt;Este es el panorama con el que me encuentro en mi nuevo trabajo. Son muchos desafíos, pero el reto es interesante.&lt;/p&gt;

&lt;p&gt;Las primeras acciones que estoy tomando se centran en establecer acuerdos con el Product Owner para adoptar Scrum de manera adecuada y mejorar la elaboración de las historias de usuario. Con el equipo, planeamos realizar una limpieza de los repositorios y aplicar una estandarización en la escritura de código utilizando "Standard.js". Además, estamos explorando extensiones en VSCode para mejorar la claridad en el desarrollo.&lt;/p&gt;

&lt;p&gt;En cuanto a la infraestructura, estoy llevando a cabo investigaciones para identificar mejoras que puedan representar cambios significativos pero que sean simples de implementar. La primera acción fue activar el Performance Insights de RDS y realizar la indexación de dos columnas en una base de datos, lo que resultó en una reducción del 50% en el uso de la CPU del servidor.&lt;/p&gt;

&lt;p&gt;Tengo previsto implementar un sistema de pull request en GitHub antes de que finalice esta semana, con la esperanza de tener la estrategia "ship-show-ask" en todos los repositorios antes de que termine el año.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>work</category>
      <category>cicd</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Entendiendo DevOps: Más Allá de los Mitos</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Fri, 19 Jan 2024 17:46:24 +0000</pubDate>
      <link>https://dev.to/norman404/entendiendo-devops-mas-alla-de-los-mitos-2h3l</link>
      <guid>https://dev.to/norman404/entendiendo-devops-mas-alla-de-los-mitos-2h3l</guid>
      <description>&lt;p&gt;DevOps se ha convertido en una palabra clave en el mundo de la tecnología, pero ¿qué significa realmente? Más importante aún, ¿qué no significa? En esta post, vamos a desmitificar DevOps y explorar algunas de las mejores prácticas que pueden ayudar a tu equipo a triunfar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24zt5t9zsdpwsaoij528.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24zt5t9zsdpwsaoij528.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué es DevOps?
&lt;/h2&gt;

&lt;p&gt;DevOps es una cultura, un movimiento, una filosofía. Nace de la fusión de 'Desarrollo' y 'Operaciones', enfocándose en la colaboración, automatización, integración continua, entrega continua y monitoreo constante de software a lo largo de todo su ciclo de vida.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buenas Prácticas en DevOps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Colaboración Continua&lt;/strong&gt;: El corazón de DevOps es la colaboración entre equipos. Romper las barreras entre desarrolladores y operaciones es fundamental. La comunicación constante y efectiva mejora la comprensión y eficiencia del equipo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatización Integral&lt;/strong&gt;: Automatiza todo lo que puedas. Desde pruebas de código, integración, despliegue, hasta monitoreo y retroalimentación. Esto no solo ahorra tiempo, sino que también reduce errores humanos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integración y Entrega Continua (CI/CD)&lt;/strong&gt;: Implementa CI/CD para integrar y desplegar código frecuentemente. Esto permite detectar errores temprano y acelera la entrega de nuevas funciones y actualizaciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoreo y Retroalimentación Continuos&lt;/strong&gt;: Monitorea constantemente el rendimiento del software y recoge retroalimentación para mejorar. Esto ayuda a anticipar problemas antes de que afecten a los usuarios.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lo que DevOps No Es:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No es solo una herramienta&lt;/strong&gt;: Aunque las herramientas son importantes, DevOps es principalmente una cultura y un conjunto de prácticas. No se trata de comprar la última herramienta de moda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No es solo para grandes equipos o empresas&lt;/strong&gt;: DevOps beneficia a organizaciones de todos los tamaños. La clave es adaptar las prácticas a las necesidades y capacidad de tu equipo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No es una solución mágica&lt;/strong&gt;: Implementar DevOps no resuelve automáticamente todos los problemas. Requiere compromiso, adaptación y aprendizaje continuo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DevOps es un viaje, no un destino. Se trata de mejorar continuamente las prácticas de desarrollo y operaciones para ofrecer mejor software, más rápido y de manera más eficiente. Al entender lo que DevOps es y lo que no es, podemos comenzar a implementar prácticas que realmente marquen la diferencia en nuestros proyectos.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>culture</category>
    </item>
    <item>
      <title>Aplicando la Ciencia de Datos en DevOps: La Era del Data-Driven DevOps</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Sun, 02 Jul 2023 01:10:36 +0000</pubDate>
      <link>https://dev.to/norman404/aplicando-la-ciencia-de-datos-en-devops-la-era-del-data-driven-devops-532j</link>
      <guid>https://dev.to/norman404/aplicando-la-ciencia-de-datos-en-devops-la-era-del-data-driven-devops-532j</guid>
      <description>&lt;p&gt;La unión de dos mundos innovadores, DevOps y la ciencia de datos, ha llevado a la creación de un nuevo paradigma en el desarrollo de software: el Data-Driven DevOps. Al aplicar principios de la ciencia de datos a las operaciones de DevOps, podemos obtener una visión más profunda de nuestros procesos de desarrollo y mejorar su rendimiento y eficiencia. En este artículo, exploraremos cómo funciona esto y cómo puedes implementarlo en tus propios proyectos.&lt;/p&gt;

&lt;h2&gt;
  
  
  La era del Data-Driven DevOps
&lt;/h2&gt;

&lt;p&gt;El objetivo de DevOps es mejorar la colaboración entre los equipos de desarrollo y operaciones para lograr un flujo de trabajo más eficiente y eficaz. Con el advenimiento del Data-Driven DevOps, este objetivo se lleva un paso más allá, utilizando técnicas de ciencia de datos para informar y optimizar los procesos de DevOps.&lt;br&gt;
En la era del Data-Driven DevOps, los datos recopilados a través de diversas etapas del ciclo de vida del desarrollo de software se utilizan para alimentar modelos predictivos y prescriptivos. Estos modelos pueden ayudar a identificar posibles cuellos de botella, predecir problemas antes de que ocurran, e incluso sugerir formas de mejorar la eficiencia del proceso de desarrollo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementando la Ciencia de Datos en DevOps
&lt;/h2&gt;

&lt;p&gt;Para implementar la ciencia de datos en DevOps, es importante empezar por la recopilación de datos. Esta recopilación puede venir de múltiples fuentes, incluyendo registros de sistema, métricas de rendimiento, y registros de errores. El siguiente paso es el análisis de estos datos, que puede implicar técnicas estadísticas, aprendizaje automático, e incluso inteligencia artificial.&lt;br&gt;
Por ejemplo, puedes utilizar el análisis de series temporales para identificar patrones en los registros de sistema, lo que podría ayudarte a predecir cuándo es probable que ocurran ciertos problemas. O puedes utilizar algoritmos de aprendizaje automático para analizar los registros de errores y identificar las causas más comunes de fallos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;El Data-Driven DevOps representa un nuevo horizonte en el desarrollo de software. Al combinar los principios de la ciencia de datos con las prácticas de DevOps, podemos mejorar la eficiencia de nuestros procesos de desarrollo y proporcionar un mejor servicio a nuestros usuarios.&lt;br&gt;
En el futuro, esperamos ver aún más innovaciones en este espacio. Así que si estás en el mundo del desarrollo de software y te interesa tanto DevOps como la ciencia de datos, definitivamente deberías considerar explorar el Data-Driven DevOps.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>data</category>
      <category>datascience</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Monitoreo de sitios web con Upptime</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Fri, 18 Feb 2022 19:36:36 +0000</pubDate>
      <link>https://dev.to/norman404/monitoreo-de-sitios-web-con-upptime-9mi</link>
      <guid>https://dev.to/norman404/monitoreo-de-sitios-web-con-upptime-9mi</guid>
      <description>&lt;p&gt;Hace tiempo en el lugar donde trabajo desarrollamos nosotros una herramienta que nos permitía saber si un sitio estaba disponible o por algún motivo no respondía hasta hora a esta funcionando bien, solo que hemos tenido inconvenientes ya que cuando lo que fallaba era nuestra infraestructura no teníamos forma de saber y llegamos a estar hasta una hora sin darnos cuenta de que no teníamos servicio.&lt;br&gt;
Para resolver ese problema me di a la tarea de buscar algo que fuera y consultara a nuestro sitio y me dijera si tenia respuesta, todo lo que encontraba era de pago o tenia que montarlo sobre nuestra infraestructura lo cual no era la idea, hasta que di con Upptime y es magia lo que hace.&lt;/p&gt;

&lt;p&gt;Upptime es un proyecto open source que usa el poder de Github para monitorear los sitios que sean agregados en su archivo de configuración.&lt;/p&gt;

&lt;p&gt;Esto me permitió tener un sitio de estatus en menos de 10 minutos con un sistema de envío de mensajes a slack, levantamiento de Issues, notificación al correo de los interesados y historial de tiempos de respuesta.&lt;/p&gt;

&lt;p&gt;Todo esto gratis.&lt;br&gt;
En otra publicación are un tutorial de configuración.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://upptime.js.org/"&gt;Upptime&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Pipeline manuales en gitlab</title>
      <dc:creator>Norman Torres</dc:creator>
      <pubDate>Sat, 22 Jan 2022 23:22:08 +0000</pubDate>
      <link>https://dev.to/norman404/pipeline-manuales-en-gitlab-1hp3</link>
      <guid>https://dev.to/norman404/pipeline-manuales-en-gitlab-1hp3</guid>
      <description>&lt;p&gt;Esta semana tuve la tarea de modificar los pipelines que usamos en el trabajo para que no se despliegue en cada rama creada una API nueva, lo cual suena muy util pero ya en la practica no era tan redituable ya que no eran muy usadas y provocaban posibles errores en el cluster de K8s.&lt;/p&gt;

&lt;p&gt;Al inicio pensé que seria algo simple ya que e podido interactuar con los archivos proporcionados por gitlab con el uso de variables de entorno pero al enfrentarme al problema descubrir que no era el caso ya que agregar una variable mataba todos los reviews lo cual no estaba buscando, entonces cambie mi problema y en lugar de permitirlo con una variable que el programador envié recordé que puedo volver manual la ejecución de alguna tarea en los pipeline dando como resultado el primer ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;include:
  - template: Auto-DevOps.gitlab-ci.yml
review:
  when: manual

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De esta forma todos los review son manuales y no se están desplegando todo el tiempo, pasando ese control al programador.&lt;/p&gt;

&lt;p&gt;Hay ocurrió otro problema ya que tenemos el habiente de dev que es llamado como review/dev lo cual hace que te pregunte si lo quieres ejecutar, eso no es le funcionamiento que necesitamos ya que al hacer un merge a dev debería de subir en automático para solucionar eso use otras reglas dando como resultado el archivo final que cumple con todas las expectativas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;review:
    rules:
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          when: never
        - if: '$CI_COMMIT_BRANCH == "dev"'
          when: on_success
        - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
          when: manual

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como últimos cambios se agrega que si el pipeline que se correo es la rama por default no se va a ejecutar el trabajo pero si es un Tag o un commit a cualquier rama se cree el trabajo pero se necesite la ejecución manual.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>pipeline</category>
      <category>gitlab</category>
    </item>
  </channel>
</rss>
