1743 words
9 minutes
My experience deploying a full-stack app on heroku with continuous deployment

Deploying a Full-Stack Application on Heroku with Continuous Deployment#

Building a full-stack application involves developing both the client-side (frontend) and server-side (backend) components. Deploying such an application requires hosting both parts and ensuring they can communicate effectively. Heroku, a cloud Platform as a Service (PaaS), simplifies this process by abstracting away much of the infrastructure management. Integrating continuous deployment (CD) automates the release cycle, ensuring that code changes are automatically built, tested (ideally), and deployed to production environments. This approach significantly accelerates the development feedback loop and improves release consistency.

Essential Concepts for Heroku Full-Stack Deployment#

Successful deployment of a full-stack application on Heroku necessitates understanding several core concepts:

  • Full-Stack Application: An application encompassing both the user interface layer (often built with frameworks like React, Angular, Vue.js) and the server-side logic, database interactions, and APIs (typically built with languages/frameworks like Node.js/Express, Python/Django/Flask, Ruby/Rails). In many full-stack deployments on Heroku, the backend serves the static frontend assets.
  • Heroku: A cloud platform that allows developers to build, run, and operate applications entirely in the cloud. It manages servers, databases, networking, and scaling.
  • Dynos: The fundamental unit of computing on Heroku. Applications run in one or more dynos, which are lightweight containers. Web dynos handle incoming HTTP requests.
  • Buildpacks: Scripts that Heroku uses to compile applications. Heroku automatically detects the language based on project files (like package.json for Node.js, requirements.txt for Python) and applies the appropriate buildpack. For full-stack apps where the backend serves the frontend, a single buildpack (e.g., Node.js) is often sufficient, managing both build processes.
  • Procfile: A plain text file named Procfile in the application’s root directory. It specifies the commands that are executed by dynos of different types. For web applications, a web process type is defined to start the web server.
  • Add-ons: Managed services provided by Heroku or third parties that add functionality to applications, such as databases (Heroku Postgres, MongoDB Atlas), monitoring tools, and caching layers.
  • Environment Variables: Configuration values that change between deployment environments (development, staging, production). Heroku allows setting these securely through its dashboard or CLI, keeping sensitive information out of source code.
  • Continuous Deployment (CD): A software release strategy where every code change that passes automated tests is automatically deployed to production. This is an extension of Continuous Integration (CI), where code changes from multiple developers are integrated frequently and automatically built and tested. Heroku facilitates CD directly from Git repositories.
  • Version Control (Git): Essential for tracking code changes, collaboration, and integrating with Heroku’s deployment process. Heroku deploys directly from a Git repository branch.

The Deployment Process: A Step-by-Step Walkthrough#

Deploying a full-stack application on Heroku with continuous deployment typically follows these steps:

  1. Prepare the Application for Heroku:

    • Configure the Backend: Ensure the backend framework is set up to run in a production environment. This often involves reading the port from an environment variable (Heroku exposes the assigned port via the PORT variable) and serving static frontend assets. For example, an Express app in Node.js might include app.listen(process.env.PORT || 3000); and middleware to serve files from a build directory (app.use(express.static(path.join(__dirname, 'client/build')));).
    • Prepare the Frontend: Configure the frontend build process. Ensure that build output (HTML, CSS, JS) is placed in a dedicated directory (commonly build or dist) within the project structure, which the backend can then serve. The frontend build command needs to be executable.
    • Define Dependencies: List all project dependencies (backend and frontend) in appropriate files (package.json, requirements.txt, etc.). Heroku uses these files to install dependencies during the build process.
    • Create a Procfile: Define how the application starts. For a web application where the backend serves the frontend, a Procfile might look like this:
      web: node index.js
      (assuming index.js is the backend entry point). If a build step is needed for the frontend before the backend starts serving, this is handled during the Heroku build process, not in the Procfile.
    • Set up Build Scripts: For Node.js full-stack apps, the package.json scripts section is crucial. Heroku’s Node.js buildpack automatically runs the heroku-postbuild script if it exists. This script is the ideal place to build the frontend. A common scripts section might look like this:
      "scripts": {
      "start": "node index.js",
      "build": "npm run build --prefix client", // Build frontend located in 'client' directory
      "heroku-postbuild": "npm install --prefix client && npm run build" // Install frontend deps and build
      },
      Note: The specific build script depends heavily on the project structure and frontend framework.
  2. Create a Git Repository: Initialize a Git repository for the project and push the code to a remote hosting service (GitHub, GitLab, Bitbucket). This is required for Heroku’s connected deployment features.

  3. Create a Heroku Application:

    • Log in to the Heroku dashboard or use the Heroku CLI (heroku create).
    • Choose a unique application name and select a region.
  4. Connect the Git Repository:

    • In the Heroku application dashboard, navigate to the “Deploy” tab.
    • Connect the Her Heroku app to the Git repository hosted on GitHub, GitLab, or Bitbucket.
  5. Configure Environment Variables:

    • Go to the “Settings” tab in the Heroku dashboard.
    • Under “Config Vars,” add any necessary environment variables (e.g., database credentials, API keys, secret keys). These are injected into the application process at runtime.
  6. Add Necessary Add-ons:

    • Go to the “Resources” tab.
    • Click “Find more add-ons” and provision necessary services like a database (e.g., Heroku Postgres). Heroku automatically adds the necessary connection details (like DATABASE_URL) to the application’s config variables.
  7. Configure Continuous Deployment:

    • Return to the “Deploy” tab.
    • In the “Automatic deploys” section, select the branch that should trigger deployments (commonly main or master).
    • Click “Enable Automatic Deploys.”
    • Optional: Enable “Wait for CI to pass before deploy” if a CI service (like GitHub Actions, CircleCI, Travis CI) is integrated with the repository to run tests. This ensures only code that passes tests is deployed.
  8. Initial Deployment:

    • For the first deployment, manually trigger a deploy from the “Manual deploy” section on the “Deploy” tab, selecting the desired branch.
    • Heroku will fetch the code, detect the buildpack, run the build process (including the heroku-postbuild script if present), and start the dyno(s) defined in the Procfile.
  9. Run Database Migrations (If Applicable):

    • If the application uses a database and has migration scripts, run them using the Heroku CLI: heroku run rails db:migrate (for Rails) or heroku run python manage.py migrate (for Django) or a custom script. This command executes the script in a one-off dyno connected to the application’s environment and database.
  10. Verify Deployment and Monitor:

    • Open the deployed application in a browser using the URL provided by Heroku.
    • Use the Heroku CLI (heroku logs --tail) or the dashboard to check application logs for errors.
    • Monitor application performance and resource usage via the Heroku dashboard.

Real-World Application Examples#

Consider two common scenarios for deploying a full-stack application on Heroku with continuous deployment:

Example 1: Node.js/Express Backend Serving a React Frontend

  • Project Structure: A typical structure might have the Express backend files in the root and the React application in a client subdirectory.
  • package.json: The root package.json includes dependencies for both backend (e.g., express) and references the frontend dependencies (e.g., React, ReactDOM) within the client/package.json. The key is the heroku-postbuild script in the root package.json:
    {
    "name": "my-fullstack-app",
    "version": "1.0.0",
    "scripts": {
    "start": "node server.js",
    "heroku-postbuild": "npm install --prefix client && npm run build --prefix client"
    },
    "dependencies": {
    "express": "^4.18.2"
    },
    "engines": {
    "node": "18.x" // Specify Node.js version
    }
    }
    The heroku-postbuild script navigates to the client directory, installs its dependencies, and runs the frontend build command (npm run build).
  • Backend Server (server.js): The Express application serves static files from the built frontend directory:
    const express = require('express');
    const path = require('path');
    const app = express();
    const PORT = process.env.PORT || 5000;
    // Serve static files from the React build directory
    app.use(express.static(path.join(__dirname, 'client/build')));
    // API routes
    app.get('/api/data', (req, res) => {
    res.json({ message: 'Data from backend' });
    });
    // Catch-all handler to serve the React app for any other request
    app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, 'client/build', 'index.html'));
    });
    app.listen(PORT, () => {
    console.log(`Server listening on port ${PORT}`);
    });
  • Procfile:
    web: node server.js
  • Heroku Configuration: Connect the GitHub repository, set necessary config vars (e.g., database URL if not Heroku Postgres), enable automatic deploys from the main branch.

With this setup, every push to the main branch on GitHub triggers Heroku. Heroku pulls the code, runs the heroku-postbuild script to build the frontend, installs backend dependencies, sets up the environment, and starts the web dyno running server.js.

Example 2: Python/Flask Backend Serving a Static HTML/JS/CSS Frontend

  • Project Structure: Flask backend files in the root, static frontend files in a static directory (standard Flask convention) or a dedicated frontend/dist directory.
  • requirements.txt: Lists Python dependencies (e.g., Flask, gunicorn). Gunicorn is a common production-ready WSGI server for Python apps on Heroku.
  • Flask App (app.py):
    from flask import Flask, send_from_directory
    import os
    app = Flask(__name__, static_folder='static') # Assumes static files in 'static'
    @app.route('/')
    def serve_index():
    # Serve index.html from the static directory
    return send_from_directory(app.static_folder, 'index.html')
    # Serve other static files automatically via Flask's static handling
    @app.route('/api/hello')
    def api_hello():
    return {'message': 'Hello from Flask backend'}
    if __name__ == '__main__':
    # When running locally, Flask's development server
    app.run(debug=True, port=os.environ.get('PORT', 5000))
    If the frontend is built into a separate dist folder (e.g., frontend/dist), the Flask app would need to be configured to serve from that directory instead of the default static folder.
  • Procfile: Use Gunicorn to serve the Flask application.
    web: gunicorn app:app --log-file - --workers 4
    app:app refers to the Flask application instance named app within the app.py file.
  • Heroku Configuration: Connect the repository, ensure requirements.txt is present for the Python buildpack, set config vars, and enable automatic deploys.

In this scenario, Heroku detects the Python app, installs dependencies from requirements.txt, and uses Gunicorn to start the Flask web server as defined in the Procfile. Flask handles serving both the static frontend files and responding to API requests.

Key Takeaways for Heroku Full-Stack Deployment with CD#

  • Backend as Frontend Server: A common and efficient pattern on Heroku is for the backend to serve the static frontend assets. This simplifies deployment by requiring only one application dyno type (web).
  • The Procfile is Critical: Clearly defines the commands Heroku executes to start your application processes. For a web application, the web process type is essential.
  • Build Scripts for Frontends: Utilize buildpack features, like the heroku-postbuild script for Node.js, to automate the frontend build process during Heroku’s build phase.
  • Environment Variables are Paramount: Never commit sensitive configuration data (API keys, database URLs) to source code. Use Heroku’s config vars feature.
  • Automate with Continuous Deployment: Connecting a Git repository and enabling automatic deploys streamlines the release cycle. Every push to the configured branch triggers a potential production update.
  • Utilize Add-ons: Integrate managed services like Heroku Postgres easily through the Add-ons marketplace, simplifying database setup and maintenance.
  • Monitor Logs: Regularly check application logs via the CLI (heroku logs) or the dashboard to diagnose issues during or after deployment.
  • Database Migrations Require Care: Running migrations is a manual step via heroku run after a successful code deployment, ensuring database schema updates are applied correctly in the production environment.
My experience deploying a full-stack app on heroku with continuous deployment
https://dev-resources.site/posts/my-experience-deploying-a-fullstack-app-on-heroku-with-continuous-deployment/
Author
Dev-Resources
Published at
2025-06-26
License
CC BY-NC-SA 4.0