This repo shows how to deploy a Next.js app and a PostgreSQL database on a Linux Ubuntu server using Docker and Nginx. It showcases several key features of Next.js like caching, ISR, environment variables, and more.
Warning
This repo is still under development.
- Purchase a domain name
- Purchase a Linux Ubuntu server (e.g. droplet)
- Create an
ADNS record pointing to your server IPv4 address
-
SSH into your server:
ssh root@your_server_ip
-
Download and run the deployment script:
curl -o ~/deploy.sh https://raw.githubusercontent.com/leerob/next-self-host/main/deploy.sh chmod +x ~/deploy.sh ./deploy.sh
I've included a Bash script which does the following:
- Installs all the necessary packages for your server
- Installs Docker, Docker Compose, and Nginx
- Clones this repository
- Generates an SSL certificate
- Builds your Next.js application from the Dockerfile
- Sets up Nginx as a reverse proxy and configures HTTPS
- Creates a
.envfile with your Postgres database creds
Once the deployment completes, your Next.js app will be available at:
http://your-provided-domain.com
Both the Next.js app and PostgreSQL database will be up and running in Docker containers.
For pushing subsequent updates, I also provided an update.sh script as an example.
This demo tries to showcase many different Next.js features.
By default, Next.js ISR uses an lru-cache and stores cached entries in memory. This works without configuration.
If you would prefer to override the location of the cache, you can optionally store these entries to storage like Redis. If you are deploying a multi-container application, you will need to use this. For this demo, it's not required.
Learn more: https://nextjs.org/docs/app/building-your-application/deploying#caching-and-isr
Next.js supports loading environment variables from .env files.
Env vars prefixed with NEXT_PUBLIC_ will be bundled and sent to the browser. app/protected/page.tsx shows an example of this. You can look at the source document to verify it has been bundled. This only makes sense for values you're comfortable being exposed to the browser.
If you want a secret env value to remain server only, you should only access it from a Server Component. app/page.tsx shows an example of this.
Learn more: https://nextjs.org/docs/app/building-your-application/deploying#environment-variables
Next.js includes an instrumentation file which can run some code when the server starts.
This value will be stabilized in Next.js 15 (which this repo is using) – the documentation currently shows it under an experimental object in next.config.js.
A common use case for this is reading secrets from a remote location, like Vault or 1Password. I've included an example with Vault, if you provide the necessary env vars to your .env file.
Learn more: https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
Next.js supports optimizing images out of the box with next start. We've made some improvements in Next.js 15 so you don't need to manually install sharp to optimize images. This means your Next.js server will use sharp and can optimize both local or remote images.
I've included a custom image loader, which will allow you to move optimization to a separate self-hosted service or cloud API if you prefer. You can uncomment that in the configuration and then modify image-loader.ts with your service.
Learn more: https://nextjs.org/docs/app/building-your-application/deploying#image-optimization
This demo has a route /protected which is guarded by Middleware.
Learn more: https://nextjs.org/docs/app/building-your-application/deploying#middleware
If you want to run this setup locally using Docker, you can follow these steps:
docker-compose upThis will start both services and make your Next.js app available at http://localhost:3000 with the PostgreSQL database running in the background.
If you encounter issues like "Internal Server Error" or Nginx returning a 502 Bad Gateway, follow these steps to troubleshoot:
-
Check Nginx Logs:
- Nginx handles reverse proxying, and if it can't reach the application, it will log the issue. You can tail the Nginx logs to see any errors:
sudo tail -f /var/log/nginx/error.log
- Look for
connect() failed (111: Connection refused)or similar errors, which suggest that Nginx is unable to reach the application.
- Nginx handles reverse proxying, and if it can't reach the application, it will log the issue. You can tail the Nginx logs to see any errors:
-
Check Docker Logs:
- If Nginx is unable to connect to the application, verify if the application container is running correctly.
- First, check the status of the Docker containers:
docker-compose ps
- If the web container isn’t running, check the logs to see why it failed:
docker-compose logs web
-
Restart Containers and Nginx:
- If the containers or Nginx are not behaving as expected, restart them:
- Restart Docker containers:
docker-compose down docker-compose up -d
- Restart Nginx:
sudo systemctl restart nginx
- Restart Docker containers:
- If the containers or Nginx are not behaving as expected, restart them:
-
Check Application Environment Variables:
- Ensure the
.envfile is correctly set up with the right database credentials and environment variables. If any variables are missing or incorrect, the app might not start properly. - You can update the
.envfile located in the app directory and then rebuild the Docker containers.
- Ensure the