Warning
This guide has moved!
It is now maintained in the official Wasp docs: 👉 https://wasp.sh/docs/guides/deployment/coolify
This gist is archived and will no longer be updated.
Warning
This guide has moved!
It is now maintained in the official Wasp docs: 👉 https://wasp.sh/docs/guides/deployment/coolify
This gist is archived and will no longer be updated.
Self-hosting is a great way to have control over your deployment infrastructure (you own the data) and a great way to keep the costs down (you pay a fixed amount per month). One of the smoothest way to manage your self-hosted deployment is Coolify.
Deploying Wasp apps to Coolify is straightforward:
It'll take you ~1 hour depending on your level of experience with servers and Github Actions.
You should have Coolify installed and set up on your server. I'll be using Hetzner to rent my server.
Follow the Coolify install instructions: https://coolify.io/self-hosted
To get Coolify apps working with your domain - you'll need to point a A record to your server IP:
To use myapp.com as your client domain, point the A record with the value of @ to your server IP.
To use api.myapp.com as your server domain, point the A record with the value of api to your server IP.
We'll set up the domains for our server and client apps below.
Tip
You can point an A record with value of coolify to your server IP and set Instance's Domain in the Settings as https://coolify.myapp.com so you can access the Coolify dashboard via the subdomain. Read more on Coolify domain setup here: https://coolify.io/docs/knowledge-base/dns-configuration
PostgreSQL
db (to keep things clean)Start to set up the databasePostgres URL (internal) - we'll need it laterDocker Image under Docker Based
ghcr.io/<your-github-username>/<something> as the Docker image name since we'll be using the Github Container Registry (ghcr.io) to store our Docker imagesghcr.io/infomiho/pokemon-server for my server image (I'll use pokemon-server as my app name later in the Github Action)server to keep things cleanDomains to https://api.<your-domain>Docker Image Tag to main since we'll use that tag laterPort Exposes to 3001Save button after you change stuffDocker Image under Docker Based
ghcr.io/infomiho/pokemon-client for my client image (I'll use pokemon-client as my app name later in the Github Action)client to keep things cleanDomains to https://<your-domain>Docker Image Tag to main since we'll use that tag laterPort Exposes to 8043Save button after you change stuffLet's go back into the server app and configure the required env vars:
Environment VariablesDATABASE_URL with value of the Postgres URL (internal)JWT_SECRET generate it with some online generatorPORT set it to 3001WASP_WEB_CLIENT_URL set it to https://<your-domain>WASP_SERVER_URL set it to https://api.<your-domain>.env.server file.github folder.github folder, create a new workflows folderdeploy.yml file from this gist to the workflows folderOnce you copy the deploy.yml, make sure to modify the:
WASP_VERSION env varSERVER_APP_NAME env var - this will be used in the Docker image nameSERVER_APP_URL env varCLIENT_APP_NAME env var - this will be used in the Docker image nameThe DOCKER_REGISTRY, DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_PASSWORD env vars will work out of the box for Github Container Registry.
Warning
If your app is located in the app folder (e.g. Open Saas has the app in the app folder) you can use the action as-is.
If your app is not in the app folder, follow the comments to modify some paths.
The Github Action depends on some repository secrets to work properly - these are some values that can't be public. You add them by going into your repository Settings and then find Secrets and variables and select Actions.
Let's add the:
SERVER_COOLIFY_WEBHOOK secret
server appWebhooks and copy the Deploy WebhookCLIENT_COOLIFY_WEBHOOK secret
client appWebhooks and copy the Deploy WebhookCOOLIFY_TOKEN secret
Settings and enable APIKeys & Tokens next and click API tokensRoot AccessYou can move your domain's nameservers to Cloudflare to get the benefits of their CDN and DDoS protections.
I've had to set my SSL mode to Full to get it working.
| name: "Deploy" | |
| on: | |
| push: | |
| branches: | |
| - "main" | |
| # This will make sure that only one deployment is running at a time | |
| concurrency: | |
| group: deployment | |
| cancel-in-progress: true | |
| env: | |
| WASP_VERSION: "0.17.0" | |
| # Put your server app name here | |
| SERVER_APP_NAME: "pokemon-server" | |
| # After you know the server URL, put the URL here | |
| SERVER_APP_URL: "https://api.<your-domain>" | |
| # Put your client app name here | |
| CLIENT_APP_NAME: "pokemon-client" | |
| DOCKER_REGISTRY: "ghcr.io" | |
| DOCKER_REGISTRY_USERNAME: ${{ github.repository_owner }} | |
| # This secret is provided by GitHub by default and is used to authenticate with the Container registry | |
| DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} | |
| jobs: | |
| build-and-push-images: | |
| permissions: | |
| contents: read | |
| packages: write | |
| runs-on: ubuntu-latest | |
| # REMOVE this whole block if your app is not in the `app` folder | |
| defaults: | |
| run: | |
| working-directory: ./app | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Log in to the Container registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ env.DOCKER_REGISTRY_USERNAME }} | |
| password: ${{ env.DOCKER_REGISTRY_PASSWORD }} | |
| - name: (server) Extract metadata for Docker | |
| id: meta-server | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_USERNAME }}/${{ env.SERVER_APP_NAME }} | |
| - name: (client) Extract metadata for Docker | |
| id: meta-client | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_USERNAME }}/${{ env.CLIENT_APP_NAME }} | |
| - name: Install Wasp | |
| shell: bash | |
| run: curl -sSL https://get.wasp.sh/installer.sh | sh -s -- -v ${{ env.WASP_VERSION }} | |
| # Insert this block if you are using Wasp TS Spec | |
| #- name: Initialize Wasp TS Spec | |
| # shell: bash | |
| # run: wasp ts-setup | |
| - name: Build Wasp app | |
| shell: bash | |
| run: wasp build | |
| - name: (client) Build | |
| shell: bash | |
| run: | | |
| cd ./.wasp/build/web-app | |
| REACT_APP_API_URL=${{ env.SERVER_APP_URL }} npm run build | |
| - name: (client) Prepare the Dockerfile | |
| shell: bash | |
| run: | | |
| cd ./.wasp/build/web-app | |
| echo "FROM pierrezemb/gostatic" > Dockerfile | |
| echo "CMD [\"-fallback\", \"index.html\", \"-enable-logging\"]" >> Dockerfile | |
| echo "COPY ./build /srv/http" >> Dockerfile | |
| - name: (server) Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| # REMOVE the `app` bit from the path if your app is not in the `app` folder | |
| context: ./app/.wasp/build | |
| # REMOVE the `app` bit from the path if your app is not in the `app` folder | |
| file: ./app/.wasp/build/Dockerfile | |
| push: true | |
| tags: ${{ steps.meta-server.outputs.tags }} | |
| labels: ${{ steps.meta-server.outputs.labels }} | |
| - name: (client) Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| # REMOVE the `app` bit from the path if your app is not in the `app` folde | |
| context: ./app/.wasp/build/web-app | |
| # REMOVE the `app` bit from the path if your app is not in the `app` folder | |
| file: ./app/.wasp/build/web-app/Dockerfile | |
| push: true | |
| tags: ${{ steps.meta-client.outputs.tags }} | |
| labels: ${{ steps.meta-client.outputs.labels }} | |
| # You can get the webhook URLs from the Coolify dashboard | |
| # Put them in the repository secrets under CLIENT_COOLIFY_WEBHOOK and SERVER_COOLIFY_WEBHOOK | |
| - name: Trigger Deploy Webhooks | |
| env: | |
| CLIENT_COOLIFY_WEBHOOK: ${{ secrets.CLIENT_COOLIFY_WEBHOOK }} | |
| SERVER_COOLIFY_WEBHOOK: ${{ secrets.SERVER_COOLIFY_WEBHOOK }} | |
| COOLIFY_TOKEN: ${{ secrets.COOLIFY_TOKEN }} | |
| run: | | |
| curl "${{ env.CLIENT_COOLIFY_WEBHOOK }}" --header 'Authorization: Bearer ${{ env.COOLIFY_TOKEN }}' | |
| curl "${{ env.SERVER_COOLIFY_WEBHOOK }}" --header 'Authorization: Bearer ${{ env.COOLIFY_TOKEN }}' |
Thank you for the update! I'll update the gist :)
ghcr.ioimage pulling will work without the explicit login if you connect your Coolify instance with a Github App in theSourcessection in Coolify. For me it just worked out of the box with a private repository when I did that step.