How to Deploy a Full-Stack App with Docker and Coolify
#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:
- Ensure you’re in the repository root
- Run: docker compose up -d —build
- Access frontend at http://localhost:3000 and backend API at http://localhost:5000
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.