Back to Shorts

Workflow

VPS Deployment Flow for Any Web App

VPS Deployment Flow for Any Web App Deploying an app to a VPS is not about one specific framework. The general flow is almost always the same. The stack can...

2 views3 min read
Workflow

VPS Deployment Flow for Any Web App

Deploying an app to a VPS is not about one specific framework.

The general flow is almost always the same.

1. Prepare the server
2. Install Docker
3. Create app Dockerfile
4. Create docker-compose.yml
5. Setup environment variables
6. Run the containers
7. Setup reverse proxy
8. Point domain to VPS
9. Install SSL certificate
10. Add deploy workflow

The stack can be different, but the deployment mindset stays the same.


1. Prepare the VPS

First, connect to the server.

Bash
ssh user@your_server_ip

Update the server packages.

Bash
sudo apt update && sudo apt upgrade -y

Install basic tools.

Bash
sudo apt install -y curl git ufw nginx

The VPS is just a remote Linux machine.
Your job is to make it able to run your application safely.


2. Install Docker

Docker helps package the application with its runtime.

So instead of installing Node, PHP, Python, or Java directly on the server, the app runs inside a container.

Bash
curl -fsSL https://get.docker.com | sh

Allow your user to run Docker without sudo.

Bash
sudo usermod -aG docker $USER

Then logout and login again.

Check installation:

Bash
docker --version
docker compose version

3. Create Application Dockerfile

Every app needs a Dockerfile.

The Dockerfile describes how the app should be built and run.

Generic example:

DOCKERFILE
FROM runtime-image

WORKDIR /app

COPY package-files .
RUN install-dependencies

COPY source-code .

RUN build-command

EXPOSE app-port

CMD ["start-command"]

For any framework, the idea is the same:

Install dependencies
Copy source code
Build the app
Run the app

4. Create docker-compose.yml

Docker Compose is used to run multiple services together.

For example:

app
database
redis
storage

Generic structure:

YAML
services:
  app:
    build: .
    container_name: my_app
    restart: unless-stopped
    ports:
      - "3000:3000"
    env_file:
      - .env

  database:
    image: postgres:16-alpine
    container_name: my_database
    restart: unless-stopped
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: app_password
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

The important part is:

Containers should be replaceable.
Data should be persistent.

That is why database data uses a volume.


5. Setup Environment Variables

Do not hardcode production config inside the source code.

Use .env.

ENV
APP_ENV=production
APP_PORT=3000
DATABASE_URL=postgresql://app_user:app_password@database:5432/app_db
APP_URL=https://yourdomain.com

Inside Docker Compose, service names can be used as hostnames.

Example:

database
redis
app

So the app can connect to PostgreSQL using:

database:5432

Not localhost.

Inside a container, localhost means the container itself.


6. Run the Application

Start the app:

Bash
docker compose up -d --build

Check running containers:

Bash
docker ps

Check logs:

Bash
docker compose logs -f app

At this point, the app should be running on the VPS.

Example:

http://your_server_ip:3000

But for production, users should access it through domain and HTTPS.


7. Setup Reverse Proxy

A reverse proxy receives public traffic and forwards it to the container.

Common tools:

Nginx
Caddy
Traefik
Apache

With Nginx, public traffic goes like this:

User
↓
Domain
↓
Nginx port 80/443
↓
Docker app port

Example Nginx config:

NGINX
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable config and reload Nginx:

Bash
sudo nginx -t
sudo systemctl reload nginx

8. Point Domain to VPS

In your DNS provider, create an A record.

Type: A
Name: @
Value: your_server_ip

For subdomain:

Type: A
Name: app
Value: your_server_ip

Then your app can be accessed from:

https://yourdomain.com
https://app.yourdomain.com

DNS points the domain to the server.
Nginx decides which app should handle the request.


9. Install SSL Certificate

Use Certbot for free SSL from Let’s Encrypt.

Bash
sudo apt install -y certbot python3-certbot-nginx

Generate SSL:

Bash
sudo certbot --nginx -d yourdomain.com

For subdomain:

Bash
sudo certbot --nginx -d app.yourdomain.com

After this, Nginx will handle HTTPS.

Check auto-renewal:

Bash
sudo certbot renew --dry-run

10. Add Deployment Workflow

Manual deployment:

Bash
git pull
docker compose up -d --build

Better deployment:

Push code to GitHub
↓
GitHub Actions builds Docker image
↓
Image pushed to registry
↓
VPS pulls latest image
↓
Docker Compose restarts app

This makes deployment more consistent because the VPS only runs the final image.

Generic production flow:

Build image in CI
Push image to registry
Pull image on server
Restart container

11. Basic Server Security

A VPS should not be left open carelessly.

Basic checklist:

Use SSH key
Disable password login
Open only needed ports
Use firewall
Keep server updated
Do not expose database port publicly

Example UFW:

Bash
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

Do not expose database like this in production:

YAML
ports:
  - "5432:5432"

Unless you really know why.

Usually, the database should only be accessible inside the Docker network.


12. Deployment Mental Model

Docker runs the app.
Compose connects the services.
Nginx exposes the app.
DNS points users to the server.
SSL secures the connection.
CI/CD makes deployment repeatable.

That is the core VPS deployment flow.

The framework can change, but the deployment architecture stays almost the same.


Untuk versi shorts portfolio, ini yang paling pas:

Deploy Any Web App to VPS

Deploying to a VPS is not tied to one framework.

Whether the app uses Node.js, Laravel, Django, Go, or Spring Boot, the production flow is usually similar.

Code
↓
Docker Image
↓
VPS
↓
Docker Compose
↓
Reverse Proxy
↓
Domain
↓
SSL

First, prepare the server.

Bash
ssh user@server_ip

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git nginx

Install Docker.

Bash
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

Create a Dockerfile.

DOCKERFILE
FROM runtime-image

WORKDIR /app

COPY dependency-files .
RUN install-dependencies

COPY source-code .

RUN build-command

EXPOSE app-port

CMD ["start-command"]

Create docker-compose.yml.

YAML
services:
  app:
    build: .
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "3000:3000"

  database:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

Run the app.

Bash
docker compose up -d --build
docker ps
docker compose logs -f app

Then put Nginx in front of the app.

NGINX
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Point the domain to the VPS.

A Record
Name: @
Value: server_ip

Install SSL.

Bash
sudo certbot --nginx -d yourdomain.com

A simple production deployment flow looks like this:

Build image
Push image to registry
Pull image on VPS
Restart container

The most important idea:

Your VPS should not depend on one framework.

It should only know how to run containers,
route traffic,
and keep the app alive.

That is why Docker, reverse proxy, domain, SSL, and environment variables are the basic building blocks of VPS deployment.

Helpful short? Tap like.

0

Comments

Join the discussion for this short.

GU

Join the discussion

Sign in first, then write a comment or reply to an existing thread.

Login required

Sign in with GitHub to start a new comment. After that, this area will become active for writing.

Your GitHub identity will be used for comment attribution.

Readers need to sign in with GitHub before they can post a comment or reply.
Loading comments...

Arya Dipanegara

Personal developer website for writing, projects, short notes, and practical software work.

Stay in touch

No newsletter flow here. Email works best for project ideas, collaborations, or quick questions.

Email me

© 2026 Arya Dipanegara. All rights reserved.