DEV Community

Maki chen
Maki chen

Posted on

How We Deploy Full-Stack Apps in 2 Minutes with Jenkins + Docker

Most dev teams I've worked with spend 30-60 minutes on each deployment. SSH into the server, pull the code, rebuild, restart services, pray nothing breaks.

We got it down to 2 minutes. Fully automated. Zero SSH.

The Stack

  • Jenkins — CI/CD orchestrator
  • Docker — Containerized everything
  • GitLab — Webhook triggers on push
  • Nginx Proxy Manager — SSL + reverse proxy

The Pipeline (5 Stages)

Stage 1: Pull & Build

git pull → docker build -t app:latest .
Enter fullscreen mode Exit fullscreen mode

Jenkins pulls the latest code and builds a Docker image. No npm install on the server — everything is baked into the image.

Stage 2: Save & Transfer

docker save app:latest | gzip > app.tar.gz
scp app.tar.gz deploy-server:/opt/services/
Enter fullscreen mode Exit fullscreen mode

Why not use a registry? For small teams, direct transfer is simpler and faster.

Stage 3: Health Check

Before deploying, we verify infrastructure is alive:

  • Database container running?
  • Redis container running?
  • Network exists?

If any check fails, the pipeline stops immediately. No half-deployed states.

Stage 4: Zero-Downtime Swap

docker stop app-old
docker rm app-old
docker load < app.tar.gz
docker run -d --name app --network shared-net app:latest
Enter fullscreen mode Exit fullscreen mode

Stage 5: Cleanup

docker image prune -f
rm app.tar.gz
Enter fullscreen mode Exit fullscreen mode

The Webhook Magic

Push to dev branch → Jenkins builds and deploys to dev server.
Push to uat branch → Jenkins builds and deploys to UAT server.

No manual triggers. No Slack messages asking "can someone deploy?"

Lessons Learned

  1. Don't check for containers that don't exist — Our UAT uses RDS, not a local DB container. The pipeline kept failing because it checked for global-db. Remove checks that don't apply to the environment.

  2. SSL is not optional — Even for internal APIs. RDS requires SSL by default. Set DB_SSL=true and move on.

  3. Use variables, not hardcoded valuesredis://${REDIS_CONTAINER}:6379 not redis://global-redis:6379. Your future self will thank you.

Result

  • Dev pushes code → 2 minutes later it's live
  • UAT deployment → Same pipeline, different branch
  • Rollback → Re-run previous build

The best deployment is the one nobody has to think about.


We use this pipeline at HEY!BOSS to deploy 100+ websites and multiple backend services.

Top comments (0)