Self-hosted with Coolify

The Why?

With Bighorn on the way, I finally made the switch to a serverful environment. In doing so, I exhausted all the providers and realized in order for my startup to succeed I need full control over my app. For example, on render.com you can set up a redis instance but you aren’t able to configure its modules. It’s all managed for you with limitations. Here is where Coolify made a difference for my project. I can easily spin up my own redis or postgres databases and have full control over them inside a container. I can keep my databases on the same machine my app runs and not create internet traffic. They’re local, so nobody has access and the performance is incredible since the Redwood API just communicates with redis and postgres on the same machine. Not to mention the hidden costs serverless carries.

What it is

  • Coolify is essentially like CapRover or Dokku; a PaaS with everything you need to self-host your apps
  • Works with Docker under the hood to create containers which house your deployed apps
  • It is open-source and maintained by one dev. Currently V3 is stable and V4 is in beta.
  • Streamlines the deployment process and allows you to easily add other services like postgres, redis, s3 storage, mailers etc. without much of the groundwork you’d have to do yourself or the cost associated in other providers
  • No paywalls, and forever open-source with active development
  • Allows you to run a multi tiered architecture with remote servers (you could set up a VPS for coolify and deploy to another VPS where your app(s) are hosted)
  • Your data is private, you self-host it you own it!

Limitations

  • Docker Swarm and Kubernetes are not available
  • Standard devOps apply, so you still have a lot of work to do with backups, security and continuous maintenance of the systems and applications, ie. patching

Prerequisites

  • You’ve set up the server.js file using yarn rw setup server-file
  • You’ll need a VPS with at least 30 GB storage, 2 vCPUs and 2 GB of memory.
  • You’ll need to own your domain, but Coolify takes care of SSL through LetsEncrypt
  • Ports 22, 80 and 443 need to be open on your VPS firewall and port 8000 is needed for the initial setup
  • Set up Coolify following any guide or video (This guide only covers Redwood deployment)
    Great overview from Syntax and getting started guide
    Coolify documentation
  • SSH into your VPS and run the following command to install Coolify V4:
sudo curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

Deployment

Let’s get into the meat of this guide. I’m assuming you’ve set up your Instance’s domain (under settings), your wildcard domain (under servers) and have either Traefik or Caddy as your proxy. I tested this using Traefik.

  1. Add your project in the UI under Projects (+Add)
  2. Select the environment you want it to run (production, but you could technically create a test environment too)
  3. Add a new resource, specifically a privateGitHub repository and select the server where your app will be running (localhost or remote)
  4. Next you’ll have to set up a GitHub App that has read access to your repository so just give it a name and Coolify will take you to GitHub for authorization
  5. Now, when creating your app for deployment make sure you select Docker Compose as your build pack (I have tried Nixpacks and Dockerfile but was unable to get Redwood deployed)
  6. You should see the following screen:

Now let’s head over to your VS Code and prepare our Redwood project for deployment.

  1. Run:
yarn rw experimental setup-docker
  1. Make sure in your redwood.toml you disable fragments (There is currently a bug and your deploy will fail, see my troubleshooting thread) and that browser.open equals false.
  2. Now when Coolify creates the image and Docker container, it mounts it at /app so a lot of the configuration in the Dockerfile you have is wrong. See this updated Dockerfile for Coolify and make sure to change the API domain for your proxy target:
# base
# ----
FROM node:20-bookworm-slim as base

RUN corepack enable
RUN apt-get update && apt-get install -y \
    openssl \
    && rm -rf /var/lib/apt/lists/*

USER node
WORKDIR /app

COPY --chown=node:node .yarnrc.yml .
COPY --chown=node:node package.json .
COPY --chown=node:node api/package.json api/
COPY --chown=node:node web/package.json web/
COPY --chown=node:node yarn.lock .

RUN mkdir -p /app/.yarn/berry/index
RUN mkdir -p /app/.cache

RUN --mount=type=cache,target=/app/.yarn/berry/cache,uid=1000 \
    --mount=type=cache,target=/app/.cache,uid=1000 \
    CI=1 yarn install

COPY --chown=node:node redwood.toml .
COPY --chown=node:node graphql.config.js .

# api build
# ---------
FROM base as api_build

COPY --chown=node:node api api
RUN yarn rw build api

# web prerender build
# -------------------
FROM api_build as web_build_with_prerender

COPY --chown=node:node web web
RUN yarn rw build web

# web build
# ---------
FROM base as web_build

COPY --chown=node:node web web
RUN yarn rw build web --no-prerender

# api serve
# ---------
FROM node:20-bookworm-slim as api_serve

RUN corepack enable

RUN apt-get update && apt-get install -y \
    openssl \
    curl \
    && rm -rf /var/lib/apt/lists/*

USER node
WORKDIR /app

COPY --chown=node:node .yarnrc.yml .
COPY --chown=node:node package.json .
COPY --chown=node:node api/package.json api/
COPY --chown=node:node yarn.lock .

RUN mkdir -p /app/.yarn/berry/index
RUN mkdir -p /app/.cache

RUN --mount=type=cache,target=/app/.yarn/berry/cache,uid=1000 \
    --mount=type=cache,target=/app/.cache,uid=1000 \
    CI=1 yarn workspaces focus api --production

COPY --chown=node:node redwood.toml .
COPY --chown=node:node graphql.config.js .

COPY --chown=node:node --from=api_build /app/api/dist /app/api/dist
COPY --chown=node:node --from=api_build /app/api/db /app/api/db
COPY --chown=node:node --from=api_build /app/node_modules/.prisma /app/node_modules/.prisma

ENV NODE_ENV=production

CMD [ "./api/dist/server.js" ]

# web serve
# ---------
FROM node:20-bookworm-slim as web_serve

RUN corepack enable

USER node
WORKDIR /app

COPY --chown=node:node .yarnrc.yml .
COPY --chown=node:node package.json .
COPY --chown=node:node web/package.json web/
COPY --chown=node:node yarn.lock .

RUN mkdir -p /app/.yarn/berry/index
RUN mkdir -p /app/.cache

RUN --mount=type=cache,target=/app/.yarn/berry/cache,uid=1000 \
    --mount=type=cache,target=/app/.cache,uid=1000 \
    CI=1 yarn workspaces focus web --production

COPY --chown=node:node redwood.toml .
COPY --chown=node:node graphql.config.js .

COPY --chown=node:node --from=web_build /app/web/dist /app/web/dist

# Change this to suit your configuration but make sure you're using http
ENV NODE_ENV=production \
    API_PROXY_TARGET=http://api.app.name:8911

CMD "node_modules/.bin/rw-web-server" "--api-proxy-target" "$API_PROXY_TARGET"

# console
# -------
FROM base as console

USER node

COPY --chown=node:node api api
COPY --chown=node:node web web
COPY --chown=node:node scripts scripts
  1. Next, let’s edit the docker-compose.prod.yml and also make sure you change the domain here and add any environment variables you’ll need:
version: "3.8"

services:
  api:
    build:
      context: .
      dockerfile: ./Dockerfile
      target: api_serve
    ports:
      - "8911:8911"
    environment:
      - NODE_ENV=production
      - DIRECT_URL=
      - DATABASE_URL=
      - SESSION_SECRET=
      - REDIS_URL=
    healthcheck:
      test: curl -f http://api.domain.name:8911/graphql/health || exit 1
      interval: 10s
      start_period: 10s
      timeout: 5s
      retries: 3

  web:
    build:
      context: .
      dockerfile: ./Dockerfile
      target: web_serve
    ports:
      - "8910:8910"
    depends_on:
      api:
        condition: service_healthy
    environment:
      - NODE_ENV=production
      - API_PROXY_TARGET=http://api.app.name:8911
  1. You’re ready to commit and send your latest code to GitHub but don’t do it yet, we have to head over to Coolify since we left the configuration empty.

  1. Add this Custom Start Command (The -d flag is necessary to run your service in the background and for your deployment to finish):
docker compose -f ./docker-compose.prod.yml up -d
  1. Make sure Auto Deploy under the Advanced tab is set to true if you wish to immediately deploy any commits to your branch. Coolify handles this with webhooks and the GitHub App we created.
  2. You don’t need any additional settings. Simply commit your code changes and wait for Coolify to pick them up.
  3. You’ll notice under the Deployments tab Coolify has begun deploying your application. Take a look inside and you should see it preparing the container with your image and work through the Dockerfile and compose commands we set up for api and web. You’ll know you’re good when the last line says “New container started.”
  4. Now head over to the Logs tab and you’ll see your server running the api and web container and their respective output. The web server should be listening on 0.0.0.0:8910 and the api server on 0.0.0.0:8911
  5. On the Command tab you have a CLI for your debian image and you can run commands by selecting the container. Note again that the working directory is /app where your files are located.
  6. The last thing you need to do is go back to the Configuration tab and set domains for your api and web containers. Make sure to use https:// here, for example:
  7. You might have to redeploy but if you see a green Running status try and visit your domains. Your app should be up and running and you’ll see incoming traffic on the Logs tab for the api container, such as auth requests. Congratulations you’ve successfully deployed your Redwood app with Coolify! If you run into trouble post in this thread and I’ll do my best to assist.

What’s not working

  • I noticed a bug deploying with graphql fragments enabled but my app still works with fragments, it’s just magical. I opened a bug issue here.
  • Coolify has a bridge network where your deployment runs under. If you need to seed your database or perform a prisma migrate you’ll have to ensure your postgres db or other services are also available in the bridge network. See my last troubleshooting post here.

Extra services

I do not recommend adding a postgres or redis into your project deployment (the docker-compose.yaml file I provided above). These should be separate resources you can add through the Coolify UI. You’ll want to know which service is and is not running, so it’s best to split them in different resources but still in the same project. You’ll have to enable the “Connect To Predefined Network” under Advanced in your redwood deployment configuration if you want your api to be able to reach other resources such as redis and postgres.

The only issue I’ve ran into is that the redis resource from Coolify appends the --appendonly yes flag, so you’ll always have AOF. If you do not want redis persistence you’ll have to create your own docker-compose for redis and add it that way. You also can’t change the default password with the redis instance you’re adding from the Coolify UI.

Thank You

Finally I want to thank everyone who worked on the Dockerfile and compose yaml files to give me a starting point to do this and @xmascooking for help and input in the original troubleshooting thread. Try it out and see how you like, sooner or later you’ll have to make a switch to a serverful environment. Why not save money and have full control over your business?

6 Likes