Deploying ApostropheCMS + Astro Projects
Now that you've built your site with ApostropheCMS and Astro, it's time to deploy it for the world to see. The Apollo project's two-application structure (backend + frontend) offers flexibility but also requires special considerations for deployment.
Understanding Deployment Options
There are two main approaches to deploying your ApostropheCMS + Astro project:
Unified Deployment (via Apostrophe Hosting or hosting that supports Node.js)
- Deploy both frontend and backend together
- Simplest option with minimal configuration
- Managed infrastructure with Apostrophe Hosting
Split Deployment
- Deploy backend and frontend to separate services
- More flexibility and control
- Requires more configuration and coordination
Start with unified deployment unless you have a reason not to. It has one thing to deploy, the backend stays private, and requests between the two applications never leave the machine. Split deployment makes sense when your backend platform can't run both applications.
Serving production as a static build doesn't remove this choice. Editors still need a server-rendered pre-production site, made up of the backend and an Astro SSR frontend, deployed in one of these two ways. The static build is an additional deployment, published to file hosting whenever content is ready.
Before You Deploy
Whichever option you choose, the project needs the same production settings. Hosting ApostropheCMS + Astro in production explains each one, and the walkthroughs on this page assume you have worked through it. In particular, make sure you have:
- A database. MongoDB is the default, and SQLite or PostgreSQL also work. See Choosing a database.
- Media storage that survives a deployment, either cloud storage such as S3 or a directory outside the project. See Media and caching.
- Every public hostname in
security.allowedDomainsinfrontend/astro.config.mjs. Without it, a production build rejects logout, file uploads, and similar requests. See Configuring Astro for your domain. - The environment variables for each application, including
APOS_HOSTin the frontend's build environment, because it is written into the build. See Environment variables.
Some platforms assign the port through PORT, or need the Astro server to listen on all interfaces. With the Node adapter, set HOST=0.0.0.0 for those platforms.
Test the production build locally before deploying. From the project root, build both applications, then start each one in its own terminal:
npm run build
npm run serve-backend
npm run serve-frontendBefore going live, run through the guide's production checklist. If you prefer, we can handle all of these details for you via our Managed Hosting.
Deploying with ApostropheCMS Hosting
Apostrophe offers a straightforward hosting solution specifically designed for ApostropheCMS projects, including those with Astro frontends.
Benefits
- Zero configuration for ApostropheCMS + Astro integration
- Database provisioning and management handled automatically
- Built-in asset storage and delivery
- SSL certificate management
- Automatic backups
- Server and operating system security updates
Getting Started with Apostrophe Hosting
Contact the Apostrophe team through their website to set up your hosting. The team will provide detailed instructions for connecting your repository for automatic deployment.
Unified Deployment on Your Own Server
The unified approach also works on a server you manage yourself. Both applications run on one machine, NGINX forwards public traffic to Astro, and ApostropheCMS listens on a private port that only Astro can reach. Hosting ApostropheCMS + Astro in production explains each part of this setup. The example below puts the parts together in order.
Example: Deploying to a DigitalOcean Droplet
This example uses SQLite, so there is no database server to install. Only the first step is specific to DigitalOcean. The rest applies to any Ubuntu server.
Create the Droplet and point your domain at it. Choose Ubuntu 24.04 LTS with at least 2 GB of RAM, and add your SSH key. Then add a DNS
Arecord for your hostname that points to the Droplet's IP address. Do this first, because the HTTPS step needs the hostname to resolve.Install the server packages. Connect with
ssh root@your-droplet-ipand run:shapt-get update && apt-get -y upgrade apt-get install -y nginx git build-essential certbot python3-certbot-nginx # Node.js from NodeSource, not Ubuntu's own nodejs package curl -fsSL https://deb.nodesource.com/setup_24.x | bash - apt-get install -y nodejs npm install -g pm2 # A non-root user to run the site useradd nodeapps -d /home/nodeapps -m -s /bin/bashClone the project and install dependencies as the new user. The starter kits install the
backendandfrontenddependencies from the project root:shsu - nodeapps git clone https://github.com/your-organization/your-project.git cd your-project npm installCreate the data directory and environment files. The database and uploaded media live in
~/data, outside the project directory, so they survive redeploys. Link the uploads folder into the project, then generate the shared key once and write it to both environment files:shmkdir -p ~/data/uploads ln -s ~/data/uploads backend/public/uploads KEY=$(openssl rand -hex 32) cat > backend/.env <<EOF APOS_DB_URI=sqlite:///home/nodeapps/data/your-project.db APOS_EXTERNAL_FRONT_KEY=$KEY APOS_SESSION_SECRET=$(openssl rand -hex 32) APOS_BASE_URL=https://www.example.com EOF cat > frontend/.env <<EOF APOS_EXTERNAL_FRONT_KEY=$KEY EOFAdd your domain to
security.allowedDomainsinfrontend/astro.config.mjs, as shown in Configuring Astro for your domain. Commit this to your repository so every deployment includes it.Build both applications, run migrations, and create an admin user:
shcd ~/your-project/backend npm run build npm run migrate cd ~/your-project/frontend APOS_HOST=http://127.0.0.1:3000 npm run build cd ~/your-project/backend NODE_ENV=production node app @apostrophecms/user:add admin adminAPOS_HOSTis written into the Astro build, so it must be set on the build command. Because the project is a git checkout, ApostropheCMS takes its release ID from the current commit, and you don't need to setAPOS_RELEASE_ID.Start both applications with PM2. Save the
ecosystem.config.cjsfile from Running the processes in the project root, then:shcd ~/your-project pm2 start ecosystem.config.cjs pm2 save exitBack in the root shell, have PM2 start the site whenever the server boots:
shenv PATH=$PATH:/usr/bin pm2 startup systemd -u nodeapps --hp /home/nodeappsConfigure NGINX. Remove the default site, save the configuration from Configuring the reverse proxy as
/etc/nginx/conf.d/your-project.confwith your own hostname, and reload:shrm -f /etc/nginx/sites-enabled/default nginx -t && systemctl reload nginxAdd HTTPS. Certbot obtains a certificate, adds it to the NGINX configuration, and redirects HTTP to HTTPS:
shcertbot --nginx -d www.example.comTurn on the firewall so that only SSH, HTTP, and HTTPS are reachable. Allow SSH before enabling it, or you will lock yourself out:
shufw allow OpenSSH ufw allow 'Nginx Full' ufw enableCheck the result. Open the site over HTTPS, log in, upload an image larger than 1 MB, and log out. Then reboot the server and confirm the site comes back by itself.
To deploy an update, pull the new code, run npm install, repeat step 6 without the last command, and run pm2 reload ecosystem.config.cjs.
INFO
In this setup, uploaded images and files are stored on the server's disk, in ~/data/uploads. Git ignores the backend/public/uploads link, so pulling new code leaves it in place. If you ever deploy from a fresh clone, recreate the link before restarting, as shown in Media and caching. Include ~/data in your backups. Switch to cloud storage such as Amazon S3 if you plan to run the backend on more than one server, deploy by replacing the project directory, or want media to survive the loss of the server.
Split Deployment (Separate Backend and Frontend)
For more control or to leverage specific platform features, you can deploy the backend and frontend separately. Compared with a unified deployment, expect these costs:
- The backend must be reachable from the internet, with its own hostname and HTTPS certificate
- Every uncached page view makes a network round trip from Astro to ApostropheCMS
- There are two deployments to keep in sync, with a shared key and a backend URL that is fixed when the frontend is built
- On serverless frontend hosts, uploads from the admin UI are subject to the platform's request size limit
Backend (ApostropheCMS) Deployment
Your ApostropheCMS backend requires:
- Node.js 24 LTS (22.19 is the minimum version)
- A database connection - MongoDB by default, or SQLite/PostgreSQL via the
@apostrophecms/db-connectadapter (see Using SQLite or PostgreSQL Instead of MongoDB) - Asset storage solution (S3 or equivalent cloud storage)
Common Backend Hosting Options
Traditional VPS/Dedicated Servers (DigitalOcean, Linode, AWS EC2)
- Complete control over the environment
- Requires server management knowledge
- Good for high-performance requirements
Platform as a Service (Heroku, Render, Railway)
- Simpler deployment with less configuration
- Often includes easy database integration
- Automatic scaling options
Example: Deploying to Render
- Create a new Web Service in Render
- Connect your GitHub repository
- Configure build settings:
- Root Directory:
backend - Build Command:
npm run build - Start Command:
npm run serve
- Root Directory:
- Set environment variables:
NODE_ENV=production APOS_DB_URI=your_database_connection_string APOS_EXTERNAL_FRONT_KEY=your_shared_secret_key APOS_S3_BUCKET=your-bucket-name APOS_S3_SECRET=your-s3-secret APOS_S3_KEY=your-s3-key APOS_S3_REGION=your-chosen-regionAPOS_DB_URIaccepts amongodb://,sqlite://, orpostgres://connection string, so it works regardless of which database you choose. See Using SQLite or PostgreSQL Instead of MongoDB for connection string formats.
There are several guides for other deployment options and configuring storage services in the main ApostropheCMS documentation.
Frontend (Astro) Deployment
Your Astro frontend can be deployed to any service, including our managed hosting, that supports SSR (Server-Side Rendering). Depending on the hosting provider you may also need to make changes to your astro.config.mjs file. The Astro.build site has a number of guides for deployment. The only extra consideration is that we are deploying a monorepo, so you need to take the extra steps to identify the frontend folder as the root for your Astro deployment.
Common Frontend Hosting Options
- ApostropheCMS
- Hosts the combined Astro + ApostropheCMS monorepo in one step
- Astro and ApostropheCMS run on the same server, so requests between them never leave the machine
- Configures your database and S3 storage automatically
- Provides
APOS_EXTERNAL_FRONT_KEYautomatically
Netlify
- Excellent Astro integration
- Easy setup with continuous deployment
- Great for sites with moderate traffic
Vercel
- Strong Node.js support
- Optimized for SSR applications
- Robust edge network
Cloudflare Pages
- Global CDN with edge computing
- Strong caching capabilities
- Good for high-traffic sites
Example: Deploying to Netlify
Netlify runs Astro's server-rendered pages as serverless functions, so the frontend needs Netlify's adapter instead of the Node adapter the starter kits use.
- Add the adapter from the
frontenddirectory:bashThis installsnpx astro add netlify@astrojs/netlifyand setsadapter: netlify()inastro.config.mjs. - Add
'host'to the integration'sexcludeRequestHeadersoption inastro.config.mjs. Astro and ApostropheCMS run on different hosts here, and without this change every page fails with a500error. See Certificate Errors.mjsexcludeRequestHeaders: [ 'host' ] - Commit and push both changes, then log in to your Netlify account and create a new site by connecting your GitHub repository
- Configure build settings:
- Base directory:
frontend - Build command:
npm run build - Publish directory:
frontend/dist
- Base directory:
- Set environment variables. Netlify makes them available during the build, which is when
APOS_HOSTis read. The backend must be reachable from the internet at this URL:APOS_EXTERNAL_FRONT_KEY=your_shared_secret_key APOS_HOST=https://your-backend-url.com - On the backend, set
APOS_BASE_URLto the Netlify site's public URL and restart it. Otherwise, logging in sends editors to whatever URL the backend was configured with. - Open the site in a browser where you are not logged in to Netlify. If it asks for a Netlify login or a password, change the site's visitor access setting to public in the Netlify project configuration.
WARNING
On Netlify, uploads from the admin UI pass through a serverless function, and Netlify limits the size of a request to a function. The limit is 6 MB, and less for binary files such as images. In practice, editors can upload files up to about 4 MB. If editors need to upload larger files, host the Astro frontend on a platform without this limit.
The adapter generates the function and routing configuration during the build, so you don't need a netlify.toml file or redirect rules. You also don't need to add the Netlify hostname to security.allowedDomains. Logging out and uploading files work on Netlify without it.
Troubleshooting Common Issues
Connection Problems
If your frontend can't connect to the backend:
- Verify the
APOS_HOSTenvironment variable was set correctly when the frontend was built. Changing it on a running server has no effect until you rebuild - Ensure
APOS_EXTERNAL_FRONT_KEYmatches between frontend and backend. A mismatch makes ApostropheCMS respond with403 forbiddenand log anexternalFrontKeyInvaliderror - Check network access between your frontend and backend servers
Certificate Errors
If every page returns a 500 error and the response or the frontend logs mention the certificate, the browser's Host header is being forwarded to the backend:
Server error: Hostname/IP does not match certificate's altnames:
Host: your-site.netlify.app. is not in the cert's altnames: DNS:your-backend-url.comAdd 'host' to the integration's excludeRequestHeaders option and redeploy the frontend. This applies whenever the two applications run on different hosts.
Login Redirects to the Wrong Site
If logging in sends editors to a different URL, or images load from a different hostname, APOS_BASE_URL on the backend doesn't match the public URL of the Astro site. Set it to the frontend's public URL and restart the backend.
Header Issues
If security headers aren't propagating properly, check your includeResponseHeaders configuration in the Astro config.
Detailed Deployment Guides
If production is a static build, see these step-by-step guides:
Static Builds with ApostropheCMS + Astro
If you want to generate a fully static frontend at build time — outputting plain HTML, CSS, and JS that can be deployed to any static hosting platform — this guide covers everything you need to configure on both the backend and frontend.
Static Builds with ApostropheCMS + Astro
Full Static Deployment with Railway and Vercel
A complete worked example of a two-tier deployment: ApostropheCMS on Railway as the backend, Astro SSR on Vercel as an always-on pre-production site for editors, and a static production site triggered by a Vercel Deploy Hook. Covers environment variables, attachment storage, admin user creation, and a publish workflow that gives content managers deliberate control over what goes live.
ApostropheCMS + Astro: Full Static Deployment with Railway and Vercel
Conclusion
Deploying an ApostropheCMS + Astro project requires careful consideration of how the two parts interact. Whether you choose unified deployment through Apostrophe Hosting or split your frontend and backend across specialized services, the key is ensuring they can communicate securely and efficiently.
For further assistance, consider:
- Joining the ApostropheCMS Discord community
- Consulting the Astro deployment documentation
- Reaching out to the Apostrophe team for hosting solutions