How to Deploy a Full-Stack App with Docker and Coolify

Team 5 min read

#docker

#coolify

#webdev

#tutorial

Introduction

Deploying a modern full-stack application often involves multiple moving parts: a frontend, a backend, and a database. Docker helps you containerize each component, while a platform like Coolify makes it easy to deploy, manage, and scale those containers in a self-hosted environment. This post walks you through building a simple full-stack app, containerizing it with Docker, orchestrating it with Docker Compose, and deploying it with Coolify.

Prerequisites

  • Docker and Docker Compose installed on your workstation
  • Basic knowledge of Node.js and REST APIs
  • A Coolify instance running (self-hosted) or access to Coolify Cloud
  • A Git repository (optional) if you plan to deploy via Coolify from source

Project structure

A simple full-stack app with:

  • backend: Express API (Node.js)
  • frontend: Next.js app (React)
  • database: PostgreSQL

Proposed structure:

  • backend/
    • src/index.js
    • package.json
    • Dockerfile
  • frontend/
    • pages/index.js
    • package.json
    • Dockerfile
  • docker-compose.yml

Sample code: backend (Express)

backend/src/index.js

const express = require('express');
const app = express();
const port = process.env.PORT || 5000;

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.get('/api/greet', (req, res) => {
  const name = req.query.name || 'World';
  res.json({ message: `Hello, ${name}!` });
});

app.listen(port, () => {
  console.log(`Backend listening on http://localhost:${port}`);
});

backend/package.json

{
  "name": "backend",
  "version": "1.0.0",
  "type": "commonjs",
  "scripts": {
    "start": "node src/index.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

backend/Dockerfile

FROM node:18-alpine
WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

EXPOSE 5000
CMD ["node", "src/index.js"]

Sample code: frontend (Next.js)

frontend/pages/index.js

export default function Home() {
  return (
    <div style={{ padding: 32 }}>
      <h1>Full-Stack App</h1>
      <p>Welcome! This is the frontend consuming the backend API.</p>
    </div>
  );
}

frontend/package.json

{
  "name": "frontend",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "start": "next start",
    "dev": "next dev"
  },
  "dependencies": {
    "next": "14.x",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

frontend/Dockerfile

FROM node:18-alpine
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

RUN npm run build

EXPOSE 3000
CMD ["npm", "start"]

Docker Compose to run locally

docker-compose.yml

version: '3.8'
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: example
      POSTGRES_DB: app
    volumes:
      - db_data:/var/lib/postgresql/data

  backend:
    build:
      context: ./backend
    restart: always
    environment:
      DATABASE_URL: postgres://postgres:example@db:5432/app
    depends_on:
      - db
    ports:
      - "5000:5000"

  frontend:
    build:
      context: ./frontend
    restart: always
    depends_on:
      - backend
    ports:
      - "3000:3000"

volumes:
  db_data:

Run locally:

Deploying with Coolify

Coolify is a self-hosted platform to deploy and manage containerized apps. Here’s a practical path to deploy the above stack.

  1. Prepare your Docker Compose project
  • Ensure your repo contains docker-compose.yml and the subdirectories for frontend and backend as shown above.
  • Commit and push to your Git provider (GitHub, GitLab, etc.). Alternatively, you can upload a zip/tarball directly in Coolify.
  1. Create a new project in Coolify
  • In Coolify, create a new project and choose the Docker Compose deployment option.
  • If you’re connecting a repo, authorize access and select the repository that contains docker-compose.yml.
  • If you’re uploading artifacts, drag the tar/zip containing the docker-compose.yml and the app directories.
  1. Configure deployment details
  • Coolify will detect services from docker-compose.yml (db, backend, frontend).
  • Set environment variables if needed (for example, DATABASE_URL for the backend).
  • You can add environment secrets and per-service configuration in Coolify’s UI.
  1. Deploy and monitor
  • Start the deployment. Coolify will build the images (backend and frontend) and start the containers.
  • Coolify provides logs per service and a health check. Tail the logs to verify connectivity between frontend, backend, and the database.
  1. Access and TLS
  • Coolify can provision domains and TLS certificates for deployed apps (via Let’s Encrypt) if you connect a domain to your Coolify instance.
  • If you want to expose only internal endpoints during development, you can keep the default ports and use port-forwarding or a tunnel.
  1. Post-deploy considerations
  • Persist PostgreSQL data with a named volume (as in the docker-compose.yml).
  • For production, consider additional concerns: API rate limits, distributed tracing, secrets management, and caching.
  • Use Coolify’s project-level features to set up rollbacks, restarts, and automatic redeploys on git changes.

Local development tips

  • Keep Docker images small: prefer multi-stage builds and slim base images.
  • Use environment variables to swap between development and production configurations.
  • If you change backend APIs, remember to rebuild the backend image in Docker Compose.
  • Use Docker Compose profiles or separate compose files (docker-compose.override.yml) to differentiate environments.

Troubleshooting quick tips

  • Service not reachable: check docker-compose ps, verify ports, and confirm inter-service communication via container names (e.g., http://backend:5000 from within the frontend container).
  • Database connection errors: ensure the DATABASE_URL matches the db service name and credentials.
  • TLS/HTTPS issues in Coolify: verify domain DNS, and ensure the Coolify instance has access to that domain for certificate provisioning.

Next steps

  • Extend the API with authentication, logging, and schema migrations.
  • Add a caching layer (e.g., Redis) and a reverse proxy (e.g., Nginx) if you need more complex routing.
  • Explore Coolify features like staging environments, automated deployments from branches, and team access controls.