Skip to content

Example: Web app with PostgreSQL ​

This example deploys a web app that stores its data in a PostgreSQL database. The app is public, the database is only reachable by the app.

text
Internet ──HTTPS──> app (app.example.hostly.ninja, port 3000)
                     │
                     └──> db:5432 (internal, volume)

The examples use the workspace domain fragment example. Replace it and the image of the app with your own.

Paid tier

This example runs two services in one workspace, and the database needs a volume to keep its data across restarts. Both require the paid tier: the free tier allows one service per workspace and has no volumes.

1. Deploy the database ​

Create a new service with these settings:

StepSettingValue
SubdomainOnly reachable internallyon
Service namedb
High availabilityoff
ImageImagepostgres:18
Container app port5432
ConfigurationSecretDB_PASSWORD
Environment variablessee below
Volumemount path /var/lib/postgresql, 5 GB

In the configuration step, first create the database password: click Add in the Secrets panel next to the environment variables, name the secret DB_PASSWORD, and enter a strong password. Secrets belong to the workspace, so the app can use the same secret later. The password is never written into an environment variable in plain text.

TIP

Use only letters and digits in the password. Special characters such as @, / or : would have to be URL-encoded in the connection string of the app.

Then enter the environment variables. Reference the secret by dragging it into the editor or by typing ${DB_PASSWORD}:

dotenv
POSTGRES_USER=app
POSTGRES_DB=app
POSTGRES_PASSWORD=${DB_PASSWORD}

The service name db is part of the address the app uses and cannot be changed later.

After the deployment, the service details page shows Internally reachable at db:5432. The service has no public URL.

WARNING

Do not enable high availability for the database. The three instances would share one volume and corrupt the data.

2. Deploy the web app ​

Create a second service:

StepSettingValue
SubdomainOnly reachable internallyoff
Subdomainapp
Service nameapp (prefilled from the subdomain)
SSLon
ImageImageregistry.hostly.ninja/example/app:1.0.0
Container app port3000
ConfigurationEnvironment variablessee below
dotenv
NODE_ENV=production
DATABASE_URL=postgres://app:${DB_PASSWORD}@db:5432/app

The secret DB_PASSWORD created with the database already appears in the Secrets panel, so reference it the same way instead of creating it again. Hostly resolves ${DB_PASSWORD} from the secret store when the container starts. The host db is the service name of the database.

The app is now reachable at https://app.example.hostly.ninja and connects to the database internally.

Good to know ​

  • Start order: Deploy the database first. If the app starts before the database is ready, it should retry the connection instead of exiting. Otherwise, Hostly restarts it until the database accepts connections.
  • Pausing and stopping: Pausing the database keeps the volume and its data. Stopping the database deletes the volume and all data.
  • Operations: The database is a regular container. Upgrades of the PostgreSQL major version and backups are up to you, e.g. with a worker that runs pg_dump regularly.
  • Other databases work the same way: MySQL or MariaDB on 3306, MongoDB on 27017. Check the image documentation for the data directory to mount the volume at.