¿Como volverse loco desplegando Ghost?

Si os digo que me ha llevado 3 días casi completos desplegar este blog pensaréis que es la primera vez que despliego una applicación echa en node y parcialmente es cierto. Ya lo he hecho antes haciendo el Master Bootcamp de Keepcoding cuando desplegué Nodepop pero por aquel aunque hice un despliegue serio ya que blindé el servidor literalmente para que solo me respondiese al puerto ssh (no era el 22), http (80) y https (443) además de protegerlo contra pings indeseados y otras tareas más. No soy experto ni en despliegues ni en seguridad, pero sé que los “malos” no son idiotas y si ven una cerradura normal con una puerta que se está cayendo no pierden tiempo tratando de abrir una blindada con cerradura de seguridad en 6 puntos…. Es obvio, ¿no?

El caso es que quería hacer el despliegue con docker en mi vps pero me encontraba una vez tras otra con este fallo:

npm info it worked if it ends with ok  
npm info using npm@2.15.8  
npm info using node@v4.4.7  
npm info prestart ghost@0.9.0  
npm info start ghost@0.9.0

> ghost@0.9.0 start /usr/src/ghost
> node index

ERROR: Unable to access Ghost's content path:  
  EACCES: permission denied, open '/usr/src/ghost/content/apps/28efa2de493dc819'

Check that the content path exists and file system permissions are correct.  
Help and documentation can be found at http://support.ghost.org.

npm info ghost@0.9.0 Failed to exec start script  
npm ERR! Linux 4.4.12-boot2docker  
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "start"  
npm ERR! node v4.4.7  
npm ERR! npm  v2.15.8  
npm ERR! code ELIFECYCLE  
npm ERR! ghost@0.9.0 start: `node index`  
npm ERR! Exit status 235  
npm ERR!  
npm ERR! Failed at the ghost@0.9.0 start script 'node index'.  
npm ERR! This is most likely a problem with the ghost package,  
npm ERR! not with npm itself.  
npm ERR! Tell the author that this fails on your system:  
npm ERR!     node index  
npm ERR! You can get information on how to open an issue for this project with:  
npm ERR!     npm bugs ghost  
npm ERR! Or if that isn't available, you can get their info via:  
npm ERR!  
npm ERR!     npm owner ls ghost  
npm ERR! There is likely additional logging output above.  
npm ERR! Linux 4.4.12-boot2docker  
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "start"  
npm ERR! node v4.4.7  
npm ERR! npm  v2.15.8  
npm ERR! path npm-debug.log.896050243  
npm ERR! code EACCES  
npm ERR! errno -13  
npm ERR! syscall open

npm ERR! Error: EACCES: permission denied, open 'npm-debug.log.896050243'  
npm ERR!     at Error (native)  
npm ERR!  { [Error: EACCES: permission denied, open 'npm-debug.log.896050243']  
npm ERR!   errno: -13,  
npm ERR!   code: 'EACCES',  
npm ERR!   syscall: 'open',  
npm ERR!   path: 'npm-debug.log.896050243' }  
npm ERR!  
npm ERR! Please try running this command again as root/Administrator.

npm ERR! Please include the following file with any support request:  
npm ERR!     /usr/src/ghost/npm-debug.log  

Buscando soluciones

Si estás aquí seguramente te suene el problema, en el “issues” del Dockerfile de Ghost hay algún mensaje al respecto y en el repo de Ghost también hay alguno que otro preguntando ya desde la versión 0.8.0, también hay varias páginas en las que se dice como arreglar el problema pero en realidad ninguno te cuenta por que, excepto [el blog de Adrián Pérez][1] y si tu despliegue es un poco diferente no te servirá.

La solución

El archivo de configuración

Basicamente el problema empieza por:

production: {  
        url: 'http://example.com',
        mail: {},
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost.db')
            },
            debug: false
        },

        server: {
            host: '0.0.0.0',
            port: '2368'
        }
    },

Faltan una parte esencial ahí y es decirle donde esta la carpeta content. Para ello debemos de dejar nuestro config.js así:

production: {  
        url: 'http://tristan.1k8b.com',
        mail: {},
        database: {
            client: 'sqlite3',
            connection: {
                filename: path.join(process.env.GHOST_CONTENT, '/data/ghost.db')
            },
             debug: false
         },

         server: {
             host: '0.0.0.0',
             port: '2368'
         },
         paths: {
             contentPath: path.join(process.env.GHOST_CONTENT || '/var/lib/ghost')
         }
    },

El path.join es gratis, no hace falta para nada, lo puse porque donde encontré esta solución a medias ponía exactamente:

paths: {  
    contentPath: path.join(process.env.GHOST_CONTENT)
}
La variable de entorno

Si bien el problema no acaba ahí, GHOST_CONTENT es una ENV var que figura en el Dockerfile de Ghost por lo que hay definirla para que Ghost funcione correctamente si estamos usando el archivo de configuración que nos genera. Si te has creado el tuyo propio desde 0 tú problema ya está resuelto casi con total seguridad.
El caso que para arrancar Ghost y que funcione debes usar algo tal que así:

docker run -d -p 2368:2368   
    -v /path/to/my/config.js:/var/lib/ghost/config.js 
    -v /path/to/my/content:/var/lib/ghost 
    --name="mywebsite.com_web" 
    -e GHOST_CONTENT=/var/lib/ghost 
    -e NODE_ENV=production 
    -e VIRTUAL_HOST=mywebsite.com 
    ghost

Obviamente si cuando pusiste en el archivo de configuración pusiste algo como:

contentPath: path.join(process.env.GHOST_CONTENT || '/var/lib/ghost', 'content')  

Entonces y solo entonces deberás tenerlo en cuenta para cambiar el segundo volumen de docker, pero bueno si has llegado hasta aquí seguramente sepas de que hablo o te haces una idea.

Yo me he creado un script que me arranca docker ya que uso una jerarquía de escritorios un poco rara para los sitios web, de todas formas lo del despliegue completo es para otro día… ?

Mejoras

Lo ideal en mi opinión sería tratar de combinar docker, pm2 y ghost. Opcionalmente una base de datos.

Con PM2 podríamos incluso hacer un despliegue multi-servidor y utilizando Nginx crear un balanceador de carga que nos mantendría permanentemente todo arriba aunque tuviéramos que apagar nuestro contenedor para, por ejemplo, actualizarlo. Y PM2 por supuesto se encargaría de los logs y que este todo arriba, aunque aún lo ignoro imagino que habrá alguna forma de hacer todo esto con docker también ya que en cuanto la app deja de correr el contenedor se apaga (pero –restart=always no es una opción ya que tira abajo el servidor).
Es una tarea para el futuro, por ahora me da igual que se caiga el blog.

Imagen extraída del artículo de como desplegó Ghost con Docker Adrián Pérez de recomendada lectura para aprender a desplegar ghost en un servidor en producción.