Application Hosting
August 23, 2026 · Anuron Blog
Anuron / reading room
How to Deploy a GitHub Monorepo with Frontend and Backend on Anuron Application Hosting
Take the useful parts with you.

A single GitHub repository can contain an entire product: a web frontend, an API, database migrations, shared documentation, infrastructure notes, and deployment configuration. This structure is productive for development, but it creates an important deployment question:
How do you deploy two applications from one repository without asking the hosting platform to guess which directory should run?
The reliable answer is to treat each deployable application as its own hosting resource while keeping both resources connected to the same GitHub repository. In this model, the frontend and backend share version control, but they have independent build settings, domains, ports, environment variables, health checks, and deployment history.
This guide explains that workflow using Review Flow as a worked example on Anuron Application Hosting. Review Flow contains a Next.js frontend under /frontend and a Node.js/Express backend under /backend. The example uses Dockerfiles that are already designed for production deployment.
The same approach applies to many modern full-stack repositories, including React or Next.js frontends paired with Node.js, Python, Go, or PHP APIs.
The short answer: deploy the repository twice
A monorepo is not necessarily one deployable application. It is a source-code organization strategy. Anuron Application Hosting can use the same repository for multiple application resources, with each resource assigned a different base directory. Anuron’s application configuration explicitly supports a Base Directory for monorepos, and its applications run as Docker containers.
For Review Flow, the recommended architecture is:
GitHub repository: reviewflow
│
├── frontend/ ── Dockerfile ── Next.js standalone server ── port 3000
│ │
│ └── https://flow.anuron.io
│
└── backend/ ── Dockerfile ── Express API server ── port 4000
│
└── https://api.anuron.io
The frontend calls the backend through a public HTTPS API URL. The backend uses FRONTEND_URL for browser-origin and application URL behavior. PostgreSQL and Redis remain separate runtime dependencies; they should be supplied as managed resources or secure external services rather than assumed to exist inside either application container.
This is usually better than forcing both processes into one container. Each process can be restarted, scaled, monitored, rolled back, and configured independently. It also makes failures easier to diagnose: a frontend build problem does not need to be confused with a database or API problem.
Why the first deployment attempt failed
The first deployment attempt used the repository root with automatic Nixpacks detection. That was the wrong boundary for this repository.
At the root, Review Flow contains documentation and project directories. The actual application manifests and Dockerfiles are nested:
reviewflow/
├── README.md
├── docs/
├── deploy/
├── frontend/
│ ├── package.json
│ ├── package-lock.json
│ ├── next.config.ts
│ └── Dockerfile
└── backend/
├── package.json
├── package-lock.json
├── .env.example
└── Dockerfile
When a build pack inspects /, it does not automatically know whether it should build /frontend, /backend, or both. In the failed attempt, Nixpacks inspected the root and reported that it could not detect an application provider. That was an application-boundary problem, not a GitHub authentication problem.
The general rule is simple:
If one repository contains multiple independently deployable applications, create one hosting resource per application and set the resource’s base directory to the application directory.
Anuron’s Application Hosting Dockerfile build-pack should use the same pattern: choose Dockerfile as the build pack and use a subdirectory such as /backend as the Base Directory for a monorepo.
Before you begin
You need a GitHub repository containing a deployable frontend and backend, production Dockerfiles or explicit build commands, a database, a Redis-compatible service if the application requires queues or scheduled work, and public DNS names for the applications.
For the Review Flow example, the repository already provides the following production contracts:
| Component | Technology | Container port | Production command |
|---|---|---|---|
| Frontend | Next.js 16 with standalone output | 3000 | node server.js |
| Backend | Node.js, TypeScript, Express | 4000 | node dist/server.js |
| Database | PostgreSQL | Managed/external | Supplied through DATABASE_URL |
| Queue and scheduler dependency | Redis-compatible service | Managed/external | Supplied through REDIS_URL |
Do not put database passwords, JWT secrets, SMTP passwords, OAuth secrets, payment credentials, or cloud access keys in the Git repository. Add them through the secure environment-variable interface for the relevant application.
You should also decide your public domains before creating the resources. The example uses:
| Purpose | Example hostname |
|---|---|
| Frontend and tenant application | flow.anuron.io |
| Backend API | api.anuron.io |
| Optional customer custom-domain CNAME target | cname.anuron.io |
Use your own verified domains if these names are not available for your deployment. The important relationship is that the frontend points to the API hostname and the backend allows the frontend origin.
Step 1: Confirm the repository’s deployment boundaries
Open the repository and identify every independently deployable application. Look for a package manifest, lockfile, Dockerfile, build command, start command, and listening port.
For ReviewFlow, the backend manifest defines:
{
"scripts": {
"build": "tsup src/server.ts --format esm,cjs && mkdir -p dist/assets && cp src/assets/* dist/assets/",
"start": "node dist/server.js"
}
}
The backend Dockerfile builds the TypeScript source, copies the compiled dist output into a production image, exposes port 4000, and starts node dist/server.js.
The frontend manifest defines:
{
"scripts": {
"build": "NODE_OPTIONS='--max_old_space_size=4096' next build",
"start": "next start"
}
}
The frontend Dockerfile uses Next.js standalone output. Its final image exposes port 3000, sets HOSTNAME=0.0.0.0, and starts the standalone server with node server.js. Next.js documents Docker and standalone output as supported deployment approaches for applications that need a Node.js runtime. Docker’s official Next.js guide also explains that standalone output creates a self-contained runtime suitable for a production container.
This inspection tells you that Review Flow should not be deployed as one root-level Nixpacks application. It should be deployed as two Dockerfile-based applications.
Step 2: Create the backend application first
Create a new application resource in Anuron Application Hosting and connect the private GitHub repository. For a private repository, use the GitHub App or deploy-key workflow available in the platform. The important point is that the application must use SSH transport when it relies on a private GitHub deploy key.
Configure the backend resource as follows:
| Setting | Value for ReviewFlow |
|---|---|
| Repository | Your private ReviewFlow GitHub repository |
| Branch | The branch you intend to release, for example main |
| Build pack | Dockerfile |
| Base Directory | /backend |
| Dockerfile | The Dockerfile inside /backend |
| Exposed port | 4000 |
| Public domain | https://api.anuron.io |
| Health endpoint | /health |
The exact Dockerfile field may be shown as a path relative to the repository or relative to the selected base directory. If the field is repository-relative, use /backend/Dockerfile; if the interface treats /backend as the root, use Dockerfile. The goal is the same: Anuron Application Hosting must build the Dockerfile located inside /backend, not look for a Dockerfile at the monorepo root.
Do not map the container port directly to a host port unless you have a specific reason. Let the platform proxy route the public domain to container port 4000. In Anuron Application Hosting exposed ports are the ports the container makes available to the proxy and health checks.
Step 3: Configure backend environment variables
The backend must know where the frontend lives, where the public API lives, and how to reach its database and Redis service. Add the values through the backend application’s environment settings.
The core variables are:
| Variable | Example or purpose |
|---|---|
NODE_ENV | production |
PORT | 4000 |
FRONTEND_URL | https://flow.anuron.io |
API_PUBLIC_URL | https://api.anuron.io |
DATABASE_URL | Your PostgreSQL connection URL |
REDIS_URL | Your Redis connection URL |
JWT_SECRET | A long, unique production secret |
CUSTOM_DOMAIN_CNAME_TARGET | cname.anuron.io, if customer custom domains are enabled |
ZEPTOMAIL_SMTP_HOST | smtp.zeptomail.com, if transactional email is enabled |
ZEPTOMAIL_SMTP_PORT | 587, if transactional email is enabled |
ZEPTOMAIL_SMTP_SECURE | false for the documented port-587 setup, if applicable |
ZEPTOMAIL_USERNAME | Your SMTP username |
ZEPTOMAIL_PASSWORD | Your SMTP password |
ZEPTOMAIL_FROM_ADDRESS | Your verified sender address |
SUPPORT_INBOX_EMAIL | Support inbox for ticket notifications, if used |
Review Flow’s backend uses API_PUBLIC_URL when generating public URLs such as QR-code destinations. Do not leave this set to http://localhost:4000 in production. The public API value should not include /api/v1; the frontend API client adds that path through NEXT_PUBLIC_API_URL.
The Google OAuth, HubSpot OAuth, S3, payment, and Twilio variables should be added only when the corresponding feature is configured. The correct values depend on the external provider and should never be invented for a deployment guide.
If your PostgreSQL provider requires SSL and the connection URL does not already specify the correct mode, use the project’s documented database SSL variables. Verify the provider’s connection requirements before deploying.
Step 4: Deploy and verify the backend
Start the backend deployment after reviewing the branch, base directory, Dockerfile, port, domain, and environment variables. The Dockerfile should install dependencies, compile the TypeScript source, and produce a production image.
After the container starts, check:
https://api.anuron.io/health
A healthy response should report that the API is healthy and the database check is okay. Review Flow also exposes:
https://api.anuron.io/health/db
The second endpoint is useful for confirming that the application can reach the expected schema. If /health returns a database error, the container is running but the database connection or schema is not ready. Check DATABASE_URL, SSL requirements, firewall rules, and migrations before troubleshooting the frontend.
Review Flow’s backend Dockerfile includes the migration tooling required for npm run db:push inside the runtime image. Run a schema push only when you understand the migration impact and have verified the target database. In production, database changes should be treated as a controlled release step rather than an automatic command added casually to every deployment.
Step 5: Create the frontend application from the same repository
Create a second application resource using the same GitHub repository and branch. This time, the resource must point to /frontend.
Configure it as follows:
| Setting | Value for ReviewFlow |
|---|---|
| Repository | The same Review Flow GitHub repository |
| Branch | The same release branch used by the backend |
| Build pack | Dockerfile |
| Base Directory | /frontend |
| Dockerfile | The Dockerfile inside /frontend |
| Exposed port | 3000 |
| Public domain | https://flow.anuron.io |
The frontend Dockerfile is a multi-stage build. It installs dependencies with npm ci, builds Next.js, copies the standalone output into a smaller runtime image, and starts node server.js on port 3000.
Next.js public environment variables are generally consumed at build time. Therefore, set the frontend API values before building the image, not only after the container has started.
Step 6: Configure frontend build-time variables
Set the variables below on the frontend application:
| Variable | Production value |
|---|---|
NEXT_PUBLIC_API_URL | https://api.anuron.io/api/v1 |
NEXT_PUBLIC_ROOT_DOMAIN | flow.anuron.io |
NEXT_PUBLIC_CUSTOM_DOMAIN_CNAME_TARGET | cname.anuron.io, if customer domains are enabled |
NEXT_PUBLIC_S3_PUBLIC_URL | Your public S3/CDN URL, only if the application uses one |
The ReviewFlow frontend Dockerfile declares these values as Docker ARG instructions and promotes them to build-time environment values. Anuron’s Application Hosting Dockerfile build-pack notes that environment variables can be injected as build arguments and that this behavior can be managed from the application’s Advanced settings.
Make sure the platform’s Inject Build Args to Dockerfile option is enabled when relying on these declared ARG values, or configure the same values explicitly in the build-arguments section. If the frontend is built without NEXT_PUBLIC_API_URL, it can fall back to http://localhost:4000/api/v1, which will work only on a developer’s machine and will fail for real visitors.
After saving these values, deploy the frontend. Do not change the container start command unless you have intentionally changed the Dockerfile; the provided image is designed to start with node server.js.
Step 7: Verify frontend-to-backend connectivity
Open the frontend domain in a private browser window and verify the application can load its public pages. Then test an authenticated or API-backed flow that is safe for your environment.
Use the browser developer tools to check the network requests. The browser should request the API through:
https://api.anuron.io/api/v1/...
It should not request:
http://localhost:4000/api/v1/...
If the frontend loads but API requests fail, check the following in order:
NEXT_PUBLIC_API_URLwas set before the frontend build.- The frontend application was rebuilt after changing that variable.
- The backend domain resolves to the backend resource.
FRONTEND_URLexactly matches the public frontend origin, including HTTPS and without an unnecessary trailing slash.- The backend database and Redis connections are healthy.
- Browser requests are reaching
/api/v1rather than a private container hostname.
A successful container build is not the same as a successful application deployment. The useful test is a real browser request from the frontend to the public backend domain, followed by a successful response from the expected API route.
Step 8: Configure automatic deployments carefully
Once both applications work independently, configure automatic deployments from the GitHub branch if that workflow matches your release process. Anuron’s Application Hosting has automatic deployment as a GitHub App capability.
Because the frontend and backend share one repository, a single commit can trigger both resources. That is convenient, but it also means a backend-only change may rebuild the frontend and a frontend-only change may rebuild the backend unless path-based filtering is configured. Start with manual deployments while validating the architecture. After the release process is understood, introduce automatic deployment and define which branch represents production.
A practical release sequence is:
1. Validate the commit in CI or a staging environment.
2. Deploy the backend if its API or schema changed.
3. Confirm /health and database connectivity.
4. Deploy the frontend with the matching API URL.
5. Verify a real browser flow.
6. Keep the previous application version available for rollback where supported.
This sequence is not a replacement for your team’s release policy. It is a way to make the dependency between the frontend and backend visible.
Optional: customer custom domains
ReviewFlow’s environment contract includes CUSTOM_DOMAIN_CNAME_TARGET, and the repository includes a Caddy topology example in deploy/Caddyfile. That example points customer-facing traffic toward the frontend and the API toward the backend.
For a production customer-domain feature, confirm all of the following before publishing it as a supported Anuron workflow:
| Check | Why it matters |
|---|---|
| Customer DNS CNAME target | The customer host must resolve to the correct frontend edge. |
| TLS issuance | Arbitrary customer domains need a verified certificate strategy. |
| Host-based routing | The frontend must know which tenant belongs to the incoming hostname. |
| API origin policy | The backend must accept the intended frontend origins without becoming broadly open. |
| Platform proxy behavior | Anuron’s Application Hosting proxy and any external Caddy layer must not compete for the same hostname. |
The two-application deployment described in this article proves the frontend/API architecture. It does not, by itself, prove arbitrary custom-domain TLS and tenant routing. Treat that as a separate production capability and test it independently before promising it to customers.
Troubleshooting common monorepo deployment failures
“No provider was detected” or providers: []
The selected base directory is probably the repository root while the deployable project is nested. Set the resource base directory to /frontend or /backend, select the Dockerfile build pack, and confirm that the Dockerfile exists within the selected directory.
“Could not read Username for https://github.com”
This indicates that the build is using an HTTPS Git URL without usable credentials. For a private deploy-key workflow, the application must use SSH repository transport and the deploy key must have access to the repository. This error occurs before the application build begins; it is not caused by Nixpacks or the Node.js code.
The frontend still calls localhost
NEXT_PUBLIC_API_URL was missing or incorrect at build time, or the frontend was not rebuilt after the variable changed. Public Next.js values are part of the generated browser bundle, so restarting the old container is not enough.
The backend container starts but /health returns a database error
The process is listening, but the database dependency is not reachable or the required schema is absent. Review DATABASE_URL, provider SSL requirements, network access, migrations, and the database’s current state.
The frontend build succeeds but the browser receives CORS errors
Check that the backend’s FRONTEND_URL exactly matches the browser origin. https://flow.anuron.io and https://www.flow.anuron.io are different origins. If a staging domain is used, configure the staging frontend origin explicitly rather than copying the production value.
The Dockerfile is found but the container immediately exits
Check the runtime command and port. ReviewFlow’s frontend runtime expects node server.js on port 3000, while the backend expects node dist/server.js on port 4000. A container can build successfully and still exit if its runtime files were not copied or its start command was overridden incorrectly.
The backend needs Redis but Redis is unavailable
The server starts scheduled workers and queue-related functionality during boot. Supply a reachable REDIS_URL and verify that the Redis resource allows connections from the backend application. Do not assume redis://localhost:6379 refers to a separate Anuron’s Application Hosting Redis service; inside a container, localhost means that same container.
Security and operations checklist
Before declaring the deployment production-ready, confirm that secrets exist only in secure environment storage, HTTPS is enabled for both public domains, the GitHub deploy key is scoped to the intended repository, database backups are available, and the API’s public exposure is intentional.
Also review the application’s logging, rate limiting, authentication expiry, file-upload storage, email sender verification, OAuth redirect URLs, and payment-provider configuration. A green container status is only one part of a production readiness review.
For the ReviewFlow example, keep the following operational boundaries visible:
| Boundary | Recommended practice |
|---|---|
| GitHub | Use a repository-scoped deploy key or GitHub App; do not put a personal password in deployment configuration. |
| Secrets | Store credentials in Anuron’s Application Hosting environment settings, never in .env committed to Git. |
| Database | Use a managed PostgreSQL service or a separately managed database resource with backups. |
| Redis | Use a reachable Redis resource and a production REDIS_URL; do not use container-local defaults. |
| Frontend configuration | Treat NEXT_PUBLIC_* values as public, because they are exposed to browser code. |
| API | Keep /health useful and avoid exposing sensitive diagnostic data in public responses. |
| Deployments | Deploy frontend and backend independently, then verify the browser-to-API path. |
Frequently asked questions
Can I deploy a frontend and backend from one GitHub repository?
Yes. Create separate application resources from the same repository and assign each one its own base directory. A monorepo can support many application resources; the important requirement is that each resource points to a specific deployable directory.
Do I need two GitHub repositories?
No. Separate repositories can be useful for organizational reasons, but they are not required for this architecture. ReviewFlow works as one repository with /frontend and /backend application boundaries.
Should I use Nixpacks or Dockerfiles?
Nixpacks is useful when the repository has one obvious application root and automatic detection is appropriate. For a monorepo with production Dockerfiles, Dockerfile-based deployment is more explicit and easier to reproduce. It makes the base directory, runtime image, port, and start command visible.
Can I run both processes in one Anuron’s application?
Only when the repository has an intentional root-level Docker Compose or root Dockerfile configuration that supervises both processes correctly. Otherwise, use two application resources. Two containers are easier to monitor and troubleshoot than two unrelated processes hidden inside one container.
Why must the frontend API URL be set before deployment?
The ReviewFlow Next.js build uses NEXT_PUBLIC_API_URL in code that runs in the browser. The value must be present when the frontend image is built so the generated client bundle points to the public API hostname.
What should I check first when a deployment fails?
Check the deployment log in order: repository access, selected branch, base directory, build pack, Dockerfile path, build output, exposed port, runtime command, environment variables, and health checks. This order prevents a database problem from being confused with a source-layout problem.
Closing perspective
A monorepo does not have to make deployment complicated. It only requires a clear distinction between source organization and runtime boundaries.
For ReviewFlow, the repeatable Anuron workflow is:
One GitHub repository
↓
Two application resources
↓
/frontend → Dockerfile → port 3000 → flow.anuron.io
/backend → Dockerfile → port 4000 → api.anuron.io
↓
Explicit environment variables, health checks, and database/Redis connections
The most important decision is not a clever build command. It is choosing the correct base directory for each application and making the frontend/backend contract explicit. Once the platform knows which directory it is building, which port it is exposing, and which public URL it should use, the monorepo becomes a practical foundation rather than a deployment obstacle.
If you are launching a full-stack product and want the infrastructure, deployment workflow, and application runtime in one place, explore Anuron Application Hosting and design your deployment around clear application boundaries from the beginning.
Continue reading
More from the same part of the archive.

Best Self-Hosted PaaS Platforms in 2026
A self-hosted PaaS can give developers a more convenient deployment workflow without taking away control of the underlying infrastructure. But the “best” platform depends on what you value most: a broad service catalog, a visual dashboard, a Git-first workflow, Docker portability, multi-server support, or a provider-managed path that avoids maintaining the control plane yourself. For […]

Self-Hosted PaaS: What Is It and Why Use One?
A self-hosted PaaS is a platform-as-a-service layer that you run on infrastructure you control. It sits between your application and the underlying server, turning many manual deployment and operations tasks into a repeatable workflow. Instead of connecting to a VPS and configuring every application by hand, you use a platform interface to bring in source […]